context: using System;
  using System.Collections.Generic;
  using System.Text;
  using System.Linq;
  using NHibernate.Cache;
  using NHibernate.Engine;
  using NHibernate.Mapping;
  using NHibernate.SqlCommand;
  using NHibernate.Type;
  using NHibernate.Util;
  
  namespace NHibernate.Persister.Entity
  {
  	/// <summary>
  	/// Default implementation of the <c>ClassPersister</c> interface. Implements the
  	/// "table-per-class hierarchy" mapping strategy for an entity class.
  	/// </summary>
  	public class SingleTableEntityPersister : AbstractEntityPersister, IQueryable
  	{
  		// the class hierarchy structure
  		private readonly int joinSpan;
  		private IType[] identifierTypes;
  		private readonly string[] qualifiedTableNames;
  		private readonly bool[] isInverseTable;
  		private readonly bool[] isNullableTable;
  		private readonly string[][] keyColumnNames;
  		private readonly bool[] cascadeDeleteEnabled;
  		private readonly bool hasSequentialSelects; // TODO 6.0: Remove
  		private readonly bool _hasSequentialSelect;
  		private readonly string[] spaces;
  		private readonly string[] subclassClosure;
  		private readonly string[] subclassTableNameClosure;
  		private readonly bool[] subclassTableIsLazyClosure;
  		private readonly bool[] isInverseSubclassTable;
  		private readonly bool[] isNullableSubclassTable;
  		private readonly bool[] subclassTableSequentialSelect;
  		private readonly string[][] subclassTableKeyColumnClosure;
  		private readonly bool[] isClassOrSuperclassTable;
  
  		// properties of this class, including inherited properties
  		private readonly int[] propertyTableNumbers;
  
  		// if the id is a property of the base table eg join to property-ref
  		// if the id is not a property the value will be null
  		private readonly Dictionary<int, int> tableIdPropertyNumbers;
  
  		// the closure of all columns used by the entire hierarchy including
  		// subclasses and superclasses of this class
  		private readonly int[] subclassPropertyTableNumberClosure;
  
  		private readonly int[] subclassColumnTableNumberClosure;
  		private readonly int[] subclassFormulaTableNumberClosure;
  
  		// discriminator column
  		private readonly Dictionary<object, string> subclassesByDiscriminatorValue = new Dictionary<object, string>();
  		private readonly bool forceDiscriminator;
  		private readonly string discriminatorColumnName;
  		private readonly string discriminatorFormula;
  		private readonly string discriminatorFormulaTemplate;
  		private readonly string discriminatorAlias;
  		private readonly IType discriminatorType;
  		private readonly string discriminatorSQLValue;
  		private readonly object discriminatorValue;
  		private readonly bool discriminatorInsertable;
  
  		private readonly string[] constraintOrderedTableNames;
  		private readonly string[][] constraintOrderedKeyColumnNames;
  
  		//private readonly IDictionary propertyTableNumbersByName = new Hashtable();
  
  		private SqlString _sequentialSelectString;
  
  		private static readonly object NullDiscriminator = new object();
  		private static readonly object NotNullDiscriminator = new object();
  
  		//provided so we can join to keys other than the primary key
  		private readonly Dictionary<int, string[]> joinToKeyColumns;
  
  		public SingleTableEntityPersister(PersistentClass persistentClass, ICacheConcurrencyStrategy cache,
  																			ISessionFactoryImplementor factory, IMapping mapping)
  			: base(persistentClass, cache, factory)
  		{
  			#region CLASS + TABLE
  
  			joinSpan = persistentClass.JoinClosureSpan + 1;
  			identifierTypes = new IType[joinSpan];
  			qualifiedTableNames = new string[joinSpan];
  			isInverseTable = new bool[joinSpan];
  			isNullableTable = new bool[joinSpan];
  			keyColumnNames = new string[joinSpan][];
  			Table table = persistentClass.RootTable;
  			identifierTypes[0] = IdentifierType;
  			qualifiedTableNames[0] =table.GetQualifiedName(factory.Dialect, factory.Settings.DefaultCatalogName, factory.Settings.DefaultSchemaName);
  			isInverseTable[0] = false;
  			isNullableTable[0] = false;
  			keyColumnNames[0] = IdentifierColumnNames;
  			cascadeDeleteEnabled = new bool[joinSpan];
  
  			// Custom sql
  			customSQLInsert = new SqlString[joinSpan];
  			customSQLUpdate = new SqlString[joinSpan];
  			customSQLDelete = new SqlString[joinSpan];
  			insertCallable = new bool[joinSpan];
  			updateCallable = new bool[joinSpan];
  			deleteCallable = new bool[joinSpan];
  			insertResultCheckStyles = new ExecuteUpdateResultCheckStyle[joinSpan];
  			updateResultCheckStyles = new ExecuteUpdateResultCheckStyle[joinSpan];
  			deleteResultCheckStyles = new ExecuteUpdateResultCheckStyle[joinSpan];
  
  			customSQLInsert[0] = persistentClass.CustomSQLInsert;
  			insertCallable[0] = customSQLInsert[0] != null && persistentClass.IsCustomInsertCallable;
  			insertResultCheckStyles[0] = persistentClass.CustomSQLInsertCheckStyle
  																	 ?? ExecuteUpdateResultCheckStyle.DetermineDefault(customSQLInsert[0], insertCallable[0]);
  			customSQLUpdate[0] = persistentClass.CustomSQLUpdate;
  			updateCallable[0] = customSQLUpdate[0] != null && persistentClass.IsCustomUpdateCallable;
  			updateResultCheckStyles[0] = persistentClass.CustomSQLUpdateCheckStyle
  																	 ?? ExecuteUpdateResultCheckStyle.DetermineDefault(customSQLUpdate[0], updateCallable[0]);
  			customSQLDelete[0] = persistentClass.CustomSQLDelete;
  			deleteCallable[0] = customSQLDelete[0] != null && persistentClass.IsCustomDeleteCallable;
  			deleteResultCheckStyles[0] = persistentClass.CustomSQLDeleteCheckStyle
  																	 ?? ExecuteUpdateResultCheckStyle.DetermineDefault(customSQLDelete[0], deleteCallable[0]);
  
  			#endregion
  
  			#region JOINS
  			int j = 1;
  			foreach (Join join in persistentClass.JoinClosureIterator)
  			{
  				identifierTypes[j] = join.Key.Type;
  				qualifiedTableNames[j] = join.Table.GetQualifiedName(factory.Dialect, factory.Settings.DefaultCatalogName, factory.Settings.DefaultSchemaName);
  				isInverseTable[j] = join.IsInverse;
  				isNullableTable[j] = join.IsOptional;
  				cascadeDeleteEnabled[j] = join.Key.IsCascadeDeleteEnabled && factory.Dialect.SupportsCascadeDelete;
  
  				customSQLInsert[j] = join.CustomSQLInsert;
  				insertCallable[j] = customSQLInsert[j] != null && join.IsCustomInsertCallable;
  				insertResultCheckStyles[j] = join.CustomSQLInsertCheckStyle
  																		 ??
  																		 ExecuteUpdateResultCheckStyle.DetermineDefault(customSQLInsert[j], insertCallable[j]);
  				customSQLUpdate[j] = join.CustomSQLUpdate;
  				updateCallable[j] = customSQLUpdate[j] != null && join.IsCustomUpdateCallable;
  				updateResultCheckStyles[j] = join.CustomSQLUpdateCheckStyle
  																		 ??
  																		 ExecuteUpdateResultCheckStyle.DetermineDefault(customSQLUpdate[j], updateCallable[j]);
  				customSQLDelete[j] = join.CustomSQLDelete;
  				deleteCallable[j] = customSQLDelete[j] != null && join.IsCustomDeleteCallable;
  				deleteResultCheckStyles[j] = join.CustomSQLDeleteCheckStyle
  																		 ??
  																		 ExecuteUpdateResultCheckStyle.DetermineDefault(customSQLDelete[j], deleteCallable[j]);
  
  				keyColumnNames[j] = join.Key.ColumnIterator.OfType<Column>().Select(col => col.GetQuotedName(factory.Dialect)).ToArray();
  
  				j++;
  			}
  
  			constraintOrderedTableNames = new string[qualifiedTableNames.Length];
  			constraintOrderedKeyColumnNames = new string[qualifiedTableNames.Length][];
  			for (int i = qualifiedTableNames.Length - 1, position = 0; i >= 0; i--, position++)
  			{
  				constraintOrderedTableNames[position] = qualifiedTableNames[i];
  				constraintOrderedKeyColumnNames[position] = keyColumnNames[i];
  			}
  
  			spaces = qualifiedTableNames.Concat(persistentClass.SynchronizedTables).ToArray();
  
  			bool lazyAvailable = IsInstrumented;
  
  			bool hasDeferred = false; // TODO 6.0: Remove
  			List<string> subclassTables = new List<string>();
  			List<string[]> joinKeyColumns = new List<string[]>();
  			//provided so we can join to keys other than the primary key
  			joinToKeyColumns = new Dictionary<int, string[]>();
  			//Columns that also function as Id's
  			List<Column> idColumns = new List<Column>();
  			tableIdPropertyNumbers = new Dictionary<int, int>();
  			List<bool> isConcretes = new List<bool>();
  			List<bool> isDeferreds = new List<bool>();
  			List<bool> isInverses = new List<bool>();
  			List<bool> isNullables = new List<bool>();
  			List<bool> isLazies = new List<bool>();
  			subclassTables.Add(qualifiedTableNames[0]);
  			joinKeyColumns.Add(IdentifierColumnNames);
  			isConcretes.Add(true);
  			isDeferreds.Add(false);
  			isInverses.Add(false);
  			isNullables.Add(false);
  			isLazies.Add(false);
  			foreach (Join join in persistentClass.SubclassJoinClosureIterator)
  			{
  				isConcretes.Add(persistentClass.IsClassOrSuperclassJoin(join));
  				isDeferreds.Add(join.IsSequentialSelect);
  				isInverses.Add(join.IsInverse);
  				isNullables.Add(join.IsOptional);
  				isLazies.Add(lazyAvailable && join.IsLazy);
  				if (join.IsSequentialSelect)
  					_hasSequentialSelect = true;
  				if (join.IsSequentialSelect && !persistentClass.IsClassOrSuperclassJoin(join))
  					hasDeferred = true;
  				subclassTables.Add(join.Table.GetQualifiedName(factory.Dialect, factory.Settings.DefaultCatalogName, factory.Settings.DefaultSchemaName));
  
  				var keyCols = join.Key.ColumnIterator.OfType<Column>().Select(col => col.GetQuotedName(factory.Dialect)).ToArray();
  				joinKeyColumns.Add(keyCols);
  
  				//are we joining to other than the primary key?
  				if (join.RefIdProperty != null)
  				{
  					var curTableIndex = joinKeyColumns.Count - 1;
  					//there should only ever be one key
  					var toKeyCols = new List<string>(join.RefIdProperty.ColumnSpan);
  					foreach (Column col in join.RefIdProperty.ColumnIterator)
  					{
  						toKeyCols.Add(col.GetQuotedName(factory.Dialect));
  						
  						//find out what property index this is
  						int i = 0;
  						foreach (var prop in persistentClass.PropertyClosureIterator)
  						{
  							if (prop == @join.RefIdProperty)
  							{
  								tableIdPropertyNumbers.Add(curTableIndex, i);
  								break;
  			}
  							i++;
  						}
  
  						idColumns.Add(col);
  					}
  					joinToKeyColumns.Add(curTableIndex, toKeyCols.ToArray());
  				}
  			}
  
  			subclassTableSequentialSelect = isDeferreds.ToArray();
  			subclassTableNameClosure = subclassTables.ToArray();
  			subclassTableIsLazyClosure = isLazies.ToArray();
  			subclassTableKeyColumnClosure = joinKeyColumns.ToArray();
  			isClassOrSuperclassTable = isConcretes.ToArray();
  			isInverseSubclassTable = isInverses.ToArray();
  			isNullableSubclassTable = isNullables.ToArray();
  			hasSequentialSelects = hasDeferred;
  
  			#endregion
  
  			#region DISCRIMINATOR
  
  			if (persistentClass.IsPolymorphic)
  			{
  				IValue discrimValue = persistentClass.Discriminator;
  				if (discrimValue == null)
  					throw new MappingException("Discriminator mapping required for single table polymorphic persistence");
  
  				forceDiscriminator = persistentClass.IsForceDiscriminator;
  				var selectable = discrimValue.ColumnIterator.First();
  				if (discrimValue.HasFormula)
  				{
  					Formula formula = (Formula)selectable;
  					discriminatorFormula = formula.FormulaString;
  					discriminatorFormulaTemplate = formula.GetTemplate(factory.Dialect, factory.SQLFunctionRegistry);
  					discriminatorColumnName = null;
  					discriminatorAlias = Discriminator_Alias;
  				}
  				else
  				{
  					Column column = (Column)selectable;
  					discriminatorColumnName = column.GetQuotedName(factory.Dialect);
  					discriminatorAlias = column.GetAlias(factory.Dialect, persistentClass.RootTable);
  					discriminatorFormula = null;
  					discriminatorFormulaTemplate = null;
  				}
  				discriminatorType = persistentClass.Discriminator.Type;
  				if (persistentClass.IsDiscriminatorValueNull)
  				{
  					discriminatorValue = NullDiscriminator;
  					discriminatorSQLValue = InFragment.Null;
  					discriminatorInsertable = false;
  				}
  				else if (persistentClass.IsDiscriminatorValueNotNull)
  				{
  					discriminatorValue = NotNullDiscriminator;
  					discriminatorSQLValue = InFragment.NotNull;
  					discriminatorInsertable = false;
  				}
  				else
  				{
  					discriminatorInsertable = persistentClass.IsDiscriminatorInsertable && !discrimValue.HasFormula;
  					try
  					{
  						IDiscriminatorType dtype = (IDiscriminatorType)discriminatorType;
  						discriminatorValue = dtype.StringToObject(persistentClass.DiscriminatorValue);
  						discriminatorSQLValue = dtype.ObjectToSQLString(discriminatorValue, factory.Dialect);
  					}
  					catch (InvalidCastException cce)
  					{
  						throw new MappingException(
  							string.Format("Illegal discriminator type: {0} of entity {1}", discriminatorType.Name, persistentClass.EntityName), cce);
  					}
  					catch (Exception e)
  					{
  						throw new MappingException("Could not format discriminator value to SQL string of entity " + persistentClass.EntityName, e);
  					}
  				}
  			}
  			else
  			{
  				forceDiscriminator = false;
  				discriminatorInsertable = false;
  				discriminatorColumnName = null;
  				discriminatorAlias = null;
  				discriminatorType = null;
  				discriminatorValue = null;
  				discriminatorSQLValue = null;
  				discriminatorFormula = null;
  				discriminatorFormulaTemplate = null;
  			}
  
  			#endregion
  
  			#region PROPERTIES
  
  			propertyTableNumbers = new int[PropertySpan];
  			int i2 = 0;
  			foreach (Property prop in persistentClass.PropertyClosureIterator)
  			{
  				propertyTableNumbers[i2++] = persistentClass.GetJoinNumber(prop);
  			}
  
  			List<int> columnJoinNumbers = new List<int>();
  			List<int> formulaJoinedNumbers = new List<int>();
  
gt: 			List<int> propertyJoinNumbers = new List<int>();