using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Text;
using NHibernate.AdoNet;
using NHibernate.Cache;
using NHibernate.Cache.Entry;
using NHibernate.Dialect.Lock;
using NHibernate.Engine;
using NHibernate.Exceptions;
using NHibernate.Id;
using NHibernate.Id.Insert;
using NHibernate.Impl;
using NHibernate.Intercept;
using NHibernate.Loader.Entity;
using NHibernate.Mapping;
using NHibernate.Metadata;
using NHibernate.Properties;
using NHibernate.SqlCommand;
using NHibernate.Tuple;
using NHibernate.Tuple.Entity;
using NHibernate.Type;
using NHibernate.Util;
using Array=System.Array;
using Property=NHibernate.Mapping.Property;
using NHibernate.SqlTypes;
using System.Linq;
namespace NHibernate.Persister.Entity
{
	/// <summary>
	/// Superclass for built-in mapping strategies. Implements functionalty common to both mapping
	/// strategies
	/// </summary>
	/// <remarks>
	/// May be considered an immutable view of the mapping object
	/// </remarks>
	public abstract class AbstractEntityPersister : IOuterJoinLoadable, IQueryable, IClassMetadata, IUniqueKeyLoadable, ISqlLoadable, ILazyPropertyInitializer, IPostInsertIdentityPersister, ILockable
	{
		#region InclusionChecker
		protected internal interface IInclusionChecker
		{
			bool IncludeProperty(int propertyNumber);
		}
		private class NoneInclusionChecker : IInclusionChecker
		{
			private readonly ValueInclusion[] inclusions;
			public NoneInclusionChecker(ValueInclusion[] inclusions)
			{
				this.inclusions = inclusions;
			}
			// TODO : currently we really do not handle ValueInclusion.PARTIAL...
			// ValueInclusion.PARTIAL would indicate parts of a component need to
			// be included in the select; currently we then just render the entire
			// component into the select clause in that case.
			public bool IncludeProperty(int propertyNumber)
			{
				return inclusions[propertyNumber] != ValueInclusion.None;
			}
		}
		private class FullInclusionChecker : IInclusionChecker
		{
			private readonly bool[] includeProperty;
			public FullInclusionChecker(bool[] includeProperty)
			{
				this.includeProperty = includeProperty;
			}
			public bool IncludeProperty(int propertyNumber)
			{
				return includeProperty[propertyNumber];
			}
		}
		#endregion
		private class GeneratedIdentifierBinder : IBinder
		{
			private readonly object[] fields;
			private readonly bool[] notNull;
			private readonly ISessionImplementor session;
			private readonly object entity;
			private readonly AbstractEntityPersister entityPersister;
			public GeneratedIdentifierBinder(object[] fields, bool[] notNull, ISessionImplementor session, object entity, AbstractEntityPersister entityPersister)
			{
				this.fields = fields;
				this.notNull = notNull;
				this.session = session;
				this.entity = entity;
				this.entityPersister = entityPersister;
			}
			public object Entity
			{
				get { return entity; }
			}
			public virtual void BindValues(DbCommand ps)
			{
				entityPersister.Dehydrate(null, fields, notNull, entityPersister.propertyColumnInsertable, 0, ps, session);
			}
		}
		private static readonly IInternalLogger log = LoggerProvider.LoggerFor(typeof(AbstractEntityPersister));
		public const string EntityClass = "class";
		protected const string Discriminator_Alias = "clazz_";
		private readonly ISessionFactoryImplementor factory;
		private readonly ICacheConcurrencyStrategy cache;
		private readonly bool isLazyPropertiesCacheable;
		private readonly ICacheEntryStructure cacheEntryStructure;
		private readonly EntityMetamodel entityMetamodel;
		private readonly Dictionary<System.Type, string> entityNameBySubclass = new Dictionary<System.Type, string>();
		private readonly string[] rootTableKeyColumnNames;
		private readonly string[] identifierAliases;
		private readonly int identifierColumnSpan;
		private readonly string versionColumnName;
		private readonly bool hasFormulaProperties;
		private readonly int batchSize;
		private readonly bool hasSubselectLoadableCollections;
		protected internal string rowIdName;
		private readonly ISet<string> lazyProperties;
		private readonly string sqlWhereString;
		private readonly string sqlWhereStringTemplate;
		#region Information about properties of this class
		//including inherited properties
		//(only really needed for updatable/insertable properties)
		private readonly int[] propertyColumnSpans;
		// the names of the columns for the property
		// the array is indexed as propertyColumnNames[propertyIndex][columnIndex] = "columnName"
		private readonly string[] propertySubclassNames;
		private readonly string[][] propertyColumnAliases;
		private readonly string[][] propertyColumnNames;
		// the alias names for the columns of the property.  This is used in the AS portion for 
		// selecting a column.  It is indexed the same as propertyColumnNames
		// private readonly string[ ] propertyFormulaTemplates;
		private readonly string[][] propertyColumnFormulaTemplates;
		private readonly bool[][] propertyColumnUpdateable;
		private readonly bool[][] propertyColumnInsertable;
		private readonly bool[] propertyUniqueness;
		private readonly bool[] propertySelectable;
		#endregion
		#region Information about lazy properties of this class
		private readonly string[] lazyPropertyNames;
		private readonly int[] lazyPropertyNumbers;
		private readonly IType[] lazyPropertyTypes;
		private readonly string[][] lazyPropertyColumnAliases;
		#endregion
		#region Information about all properties in class hierarchy
		private readonly string[] subclassPropertyNameClosure;
		private readonly string[] subclassPropertySubclassNameClosure;
		private readonly IType[] subclassPropertyTypeClosure;
		private readonly string[][] subclassPropertyFormulaTemplateClosure;
		private readonly string[][] subclassPropertyColumnNameClosure;
		private readonly FetchMode[] subclassPropertyFetchModeClosure;
		private readonly bool[] subclassPropertyNullabilityClosure;
		protected bool[] propertyDefinedOnSubclass;
		private readonly int[][] subclassPropertyColumnNumberClosure;
		private readonly int[][] subclassPropertyFormulaNumberClosure;
		private readonly CascadeStyle[] subclassPropertyCascadeStyleClosure;
		#endregion
		#region Information about all columns/formulas in class hierarchy
		private readonly string[] subclassColumnClosure;
		private readonly bool[] subclassColumnLazyClosure;
		private readonly string[] subclassColumnAliasClosure;
		private readonly bool[] subclassColumnSelectableClosure;
		private readonly string[] subclassFormulaClosure;
		private readonly string[] subclassFormulaTemplateClosure;
		private readonly string[] subclassFormulaAliasClosure;
		private readonly bool[] subclassFormulaLazyClosure;
		#endregion
		#region Dynamic filters attached to the class-level
		private readonly FilterHelper filterHelper;
		#endregion
		private readonly Dictionary<string, EntityLoader> uniqueKeyLoaders = new Dictionary<string, EntityLoader>();
		private readonly Dictionary<LockMode, ILockingStrategy> lockers = new Dictionary<LockMode, ILockingStrategy>();
		private readonly Dictionary<string, IUniqueEntityLoader> loaders = new Dictionary<string, IUniqueEntityLoader>();
		#region SQL strings
		private SqlString sqlVersionSelectString;
		private SqlString sqlSnapshotSelectString;
		private SqlString sqlLazySelectString;
		private SqlCommandInfo sqlIdentityInsertString;
		private SqlCommandInfo sqlUpdateByRowIdString;
		private SqlCommandInfo sqlLazyUpdateByRowIdString;
		private SqlCommandInfo[] sqlDeleteStrings;
		private SqlCommandInfo[] sqlInsertStrings;
		private SqlCommandInfo[] sqlUpdateStrings;
		private SqlCommandInfo[] sqlLazyUpdateStrings;
		private SqlString sqlInsertGeneratedValuesSelectString;
		private SqlString sqlUpdateGeneratedValuesSelectString;
		private string identitySelectString;
		#endregion
		#region Custom SQL
		protected internal bool[] insertCallable;
		protected internal bool[] updateCallable;
		protected internal bool[] deleteCallable;
		protected internal SqlString[] customSQLInsert;
		protected internal SqlString[] customSQLUpdate;
		protected internal SqlString[] customSQLDelete;
		protected internal ExecuteUpdateResultCheckStyle[] insertResultCheckStyles;
		protected internal ExecuteUpdateResultCheckStyle[] updateResultCheckStyles;
		protected internal ExecuteUpdateResultCheckStyle[] deleteResultCheckStyles;
		#endregion
		private IInsertGeneratedIdentifierDelegate identityDelegate;
		private bool[] tableHasColumns;
		private readonly string loaderName;
		private IUniqueEntityLoader queryLoader;
		private readonly string temporaryIdTableName;
		private readonly string temporaryIdTableDDL;
		private readonly Dictionary<string, string[]> subclassPropertyAliases = new Dictionary<string, string[]>();
		private readonly Dictionary<string, string[]> subclassPropertyColumnNames = new Dictionary<string, string[]>();
		protected readonly BasicEntityPropertyMapping propertyMapping;
		protected AbstractEntityPersister(PersistentClass persistentClass, ICacheConcurrencyStrategy cache,
																			ISessionFactoryImplementor factory)
		{
			this.factory = factory;
			this.cache = cache;
			isLazyPropertiesCacheable = persistentClass.IsLazyPropertiesCacheable;
			cacheEntryStructure = factory.Settings.IsStructuredCacheEntriesEnabled
															? (ICacheEntryStructure)new StructuredCacheEntry(this)
															: (ICacheEntryStructure)new UnstructuredCacheEntry();
			entityMetamodel = new EntityMetamodel(persistentClass, factory);
			if (persistentClass.HasPocoRepresentation)
			{
				//TODO: this is currently specific to pojos, but need to be available for all entity-modes
				foreach (Subclass subclass in persistentClass.SubclassIterator)
				{
					entityNameBySubclass[subclass.MappedClass] = subclass.EntityName;
				}
			}
			batchSize = persistentClass.BatchSize ?? factory.Settings.DefaultBatchFetchSize;
			hasSubselectLoadableCollections = persistentClass.HasSubselectLoadableCollections;
			propertyMapping = new BasicEntityPropertyMapping(this);
			#region IDENTIFIER
			identifierColumnSpan = persistentClass.Identifier.ColumnSpan;
			rootTableKeyColumnNames = new string[identifierColumnSpan];
			identifierAliases = new string[identifierColumnSpan];
			rowIdName = persistentClass.RootTable.RowId;
			loaderName = persistentClass.LoaderName;
			// TODO NH: Not safe cast to Column
			int i = 0;
			foreach (Column col in persistentClass.Identifier.ColumnIterator)
			{
				rootTableKeyColumnNames[i] = col.GetQuotedName(factory.Dialect);
				identifierAliases[i] = col.GetAlias(factory.Dialect, persistentClass.RootTable);
				i++;
			}
			#endregion
			#region VERSION
			if (persistentClass.IsVersioned)
			{
				foreach (Column col in persistentClass.Version.ColumnIterator)
				{
					versionColumnName = col.GetQuotedName(factory.Dialect);
					break; //only happens once
				}
			}
			else
			{
				versionColumnName = null;
			}
			#endregion
			#region WHERE STRING
			sqlWhereString = !string.IsNullOrEmpty(persistentClass.Where) ? "( " + persistentClass.Where + ") " : null;
			sqlWhereStringTemplate = sqlWhereString == null
																? null
																: Template.RenderWhereStringTemplate(sqlWhereString, factory.Dialect,
																																		 factory.SQLFunctionRegistry);
			#endregion
			#region PROPERTIES
			// NH: see consistence with the implementation on EntityMetamodel where we are disabling lazy-properties for no lazy entities
			bool lazyAvailable = IsInstrumented && entityMetamodel.IsLazy;
			int hydrateSpan = entityMetamodel.PropertySpan;
			propertyColumnSpans = new int[hydrateSpan];
			propertySubclassNames = new string[hydrateSpan];
			propertyColumnAliases = new string[hydrateSpan][];
			propertyColumnNames = new string[hydrateSpan][];
			propertyColumnFormulaTemplates = new string[hydrateSpan][];
			propertyUniqueness = new bool[hydrateSpan];
			propertySelectable = new bool[hydrateSpan];
			propertyColumnUpdateable = new bool[hydrateSpan][];
			propertyColumnInsertable = new bool[hydrateSpan][];
			var thisClassProperties = new HashSet<Property>();
			lazyProperties = new HashSet<string>();
			List<string> lazyNames = new List<string>();
			List<int> lazyNumbers = new List<int>();
			List<IType> lazyTypes = new List<IType>();
			List<string[]> lazyColAliases = new List<string[]>();
			i = 0;
			bool foundFormula = false;
			foreach (Property prop in persistentClass.PropertyClosureIterator)
			{
				thisClassProperties.Add(prop);
				int span = prop.ColumnSpan;
				propertyColumnSpans[i] = span;
				propertySubclassNames[i] = prop.PersistentClass.EntityName;
				string[] colNames = new string[span];
				string[] colAliases = new string[span];
				string[] templates = new string[span];
				int k = 0;
				foreach (ISelectable thing in prop.ColumnIterator)
				{
					colAliases[k] = thing.GetAlias(factory.Dialect, prop.Value.Table);
					if (thing.IsFormula)
					{
						foundFormula = true;
						templates[k] = thing.GetTemplate(factory.Dialect, factory.SQLFunctionRegistry);
					}
					else
					{
						colNames[k] = thing.GetTemplate(factory.Dialect, factory.SQLFunctionRegistry);
					}
					k++;
				}
				propertyColumnNames[i] = colNames;
				propertyColumnFormulaTemplates[i] = templates;
				propertyColumnAliases[i] = colAliases;
				if (lazyAvailable && prop.IsLazy)
				{
					lazyProperties.Add(prop.Name);
					lazyNames.Add(prop.Name);
					lazyNumbers.Add(i);
					lazyTypes.Add(prop.Value.Type);
					lazyColAliases.Add(colAliases);
				}
				propertyColumnUpdateable[i] = prop.Value.ColumnUpdateability;
				propertyColumnInsertable[i] = prop.Value.ColumnInsertability;
				propertySelectable[i] = prop.IsSelectable;
				propertyUniqueness[i] = prop.Value.IsAlternateUniqueKey;
				i++;
			}
			hasFormulaProperties = foundFormula;
			lazyPropertyColumnAliases = lazyColAliases.ToArray();
			lazyPropertyNames = lazyNames.ToArray();
			lazyPropertyNumbers = lazyNumbers.ToArray();
			lazyPropertyTypes = lazyTypes.ToArray();
			#endregion
			#region SUBCLASS PROPERTY CLOSURE
			List<string> columns = new List<string>();
			List<bool> columnsLazy = new List<bool>();
			List<string> aliases = new List<string>();
			List<string> formulas = new List<string>();
			List<string> formulaAliases = new List<string>();
			List<string> formulaTemplates = new List<string>();
			List<bool> formulasLazy = new List<bool>();
			List<IType> types = new List<IType>();
			List<string> names = new List<string>();
			List<string> classes = new List<string>();
			List<string[]> templates2 = new List<string[]>();
			List<string[]> propColumns = new List<string[]>();
			List<FetchMode> joinedFetchesList = new List<FetchMode>();
			List<CascadeStyle> cascades = new List<CascadeStyle>();
			List<bool> definedBySubclass = new List<bool>();
			List<int[]> propColumnNumbers = new List<int[]>();
			List<int[]> propFormulaNumbers = new List<int[]>();
			List<bool> columnSelectables = new List<bool>();
			List<bool> propNullables = new List<bool>();
			foreach (Property prop in persistentClass.SubclassPropertyClosureIterator)
			{
				names.Add(prop.Name);
				classes.Add(prop.PersistentClass.EntityName);
				bool isDefinedBySubclass = !thisClassProperties.Contains(prop);
				definedBySubclass.Add(isDefinedBySubclass);
				propNullables.Add(prop.IsOptional || isDefinedBySubclass); //TODO: is this completely correct?
				types.Add(prop.Type);
				string[] cols = new string[prop.ColumnSpan];
				string[] forms = new string[prop.ColumnSpan];
				int[] colnos = new int[prop.ColumnSpan];
				int[] formnos = new int[prop.ColumnSpan];
				int l = 0;
				bool lazy = prop.IsLazy && lazyAvailable;
				foreach (ISelectable thing in prop.ColumnIterator)
				{
					if (thing.IsFormula)
					{
						string template = thing.GetTemplate(factory.Dialect, factory.SQLFunctionRegistry);
						formnos[l] = formulaTemplates.Count;
						colnos[l] = -1;
						formulaTemplates.Add(template);
						forms[l] = template;
						formulas.Add(thing.GetText(factory.Dialect));
						formulaAliases.Add(thing.GetAlias(factory.Dialect));
						formulasLazy.Add(lazy);
					}
					else
					{
						string colName = thing.GetTemplate(factory.Dialect, factory.SQLFunctionRegistry);
						colnos[l] = columns.Count; //before add :-)
						formnos[l] = -1;
						columns.Add(colName);
						cols[l] = colName;
						aliases.Add(thing.GetAlias(factory.Dialect, prop.Value.Table));
						columnsLazy.Add(lazy);
						columnSelectables.Add(prop.IsSelectable);
					}
					l++;
				}
				propColumns.Add(cols);
				templates2.Add(forms);
				propColumnNumbers.Add(colnos);
				propFormulaNumbers.Add(formnos);
				joinedFetchesList.Add(prop.Value.FetchMode);
				cascades.Add(prop.CascadeStyle);
			}
			subclassColumnClosure = columns.ToArray();
			subclassColumnAliasClosure = aliases.ToArray();
			subclassColumnLazyClosure = columnsLazy.ToArray();
			subclassColumnSelectableClosure = columnSelectables.ToArray();
			subclassFormulaClosure = formulas.ToArray();
			subclassFormulaTemplateClosure = formulaTemplates.ToArray();
			subclassFormulaAliasClosure = formulaAliases.ToArray();
			subclassFormulaLazyClosure = formulasLazy.ToArray();
			subclassPropertyNameClosure = names.ToArray();
			subclassPropertySubclassNameClosure = classes.ToArray();
			subclassPropertyTypeClosure = types.ToArray();
			subclassPropertyNullabilityClosure = propNullables.ToArray();
			subclassPropertyFormulaTemplateClosure = templates2.ToArray();
			subclassPropertyColumnNameClosure = propColumns.ToArray();
			subclassPropertyColumnNumberClosure = propColumnNumbers.ToArray();
			subclassPropertyFormulaNumberClosure = propFormulaNumbers.ToArray();
			subclassPropertyCascadeStyleClosure = cascades.ToArray();
			subclassPropertyFetchModeClosure = joinedFetchesList.ToArray();
			propertyDefinedOnSubclass = definedBySubclass.ToArray();
			#endregion
			// Handle any filters applied to the class level
			filterHelper = new FilterHelper(persistentClass.FilterMap, factory.Dialect, factory.SQLFunctionRegistry);
			temporaryIdTableName = persistentClass.TemporaryIdTableName;
			temporaryIdTableDDL = persistentClass.TemporaryIdTableDDL;
		}
		protected abstract int[] SubclassColumnTableNumberClosure { get; }
		protected abstract int[] SubclassFormulaTableNumberClosure { get; }
		protected internal abstract int[] PropertyTableNumbersInSelect { get;}
		protected internal abstract int[] PropertyTableNumbers { get;}
		public virtual string DiscriminatorColumnName
		{
			get { return Discriminator_Alias; }
		}
		protected virtual string DiscriminatorFormulaTemplate
		{
			get { return null; }
		}
		public string[] RootTableKeyColumnNames
		{
			get { return rootTableKeyColumnNames; }
		}
		protected internal SqlCommandInfo[] SQLUpdateByRowIdStrings
		{
			get
			{
				if (sqlUpdateByRowIdString == null)
					throw new AssertionFailure("no update by row id");
				SqlCommandInfo[] result = new SqlCommandInfo[TableSpan + 1];
				result[0] = sqlUpdateByRowIdString;
				Array.Copy(sqlUpdateStrings, 0, result, 1, TableSpan);
				return result;
			}
		}
		protected internal SqlCommandInfo[] SQLLazyUpdateByRowIdStrings
		{
			get
			{
				if (sqlLazyUpdateByRowIdString == null)
					throw new AssertionFailure("no update by row id");
				SqlCommandInfo[] result = new SqlCommandInfo[TableSpan];
				result[0] = sqlLazyUpdateByRowIdString;
				for (int i = 1; i < TableSpan; i++)
					result[i] = sqlLazyUpdateStrings[i];
				return result;
			}
		}
		protected SqlString SQLSnapshotSelectString
		{
			get { return sqlSnapshotSelectString; }
		}
		protected SqlString SQLLazySelectString
		{
			get { return sqlLazySelectString; }
		}
		/// <summary>
		/// The queries that delete rows by id (and version)
		/// </summary>
		protected SqlCommandInfo[] SqlDeleteStrings
		{
			get { return sqlDeleteStrings; }
		}
		/// <summary>
		/// The queries that insert rows with a given id
		/// </summary>
		protected SqlCommandInfo[] SqlInsertStrings
		{
			get { return sqlInsertStrings; }
		}
		/// <summary>
		/// The queries that update rows by id (and version)
		/// </summary>
		protected SqlCommandInfo[] SqlUpdateStrings
		{
			get { return sqlUpdateStrings; }
		}
		protected internal SqlCommandInfo[] SQLLazyUpdateStrings
		{
			get { return sqlLazyUpdateStrings; }
		}
		/// <summary> 
		/// The query that inserts a row, letting the database generate an id 
		/// </summary>
		/// <returns> The IDENTITY-based insertion query. </returns>
		protected internal SqlCommandInfo SQLIdentityInsertString
		{
			get { return sqlIdentityInsertString; }
		}
		protected SqlString VersionSelectString
		{
			get { return sqlVersionSelectString; }
		}
		public bool IsBatchable => OptimisticLockMode == Versioning.OptimisticLock.None ||
		                           (!IsVersioned && OptimisticLockMode == Versioning.OptimisticLock.Version) ||
		                           Factory.Settings.IsBatchVersionedDataEnabled;
		public virtual string[] QuerySpaces
		{
			get { return PropertySpaces; }
		}
		protected internal ISet<string> LazyProperties
		{
			get { return lazyProperties; }
		}
		public bool IsBatchLoadable
		{
			get { return batchSize > 1; }
		}
		public virtual string[] IdentifierColumnNames
		{
			get { return rootTableKeyColumnNames; }
		}
		protected int IdentifierColumnSpan
		{
			get { return identifierColumnSpan; }
		}
		public virtual string VersionColumnName
		{
			get { return versionColumnName; }
		}
		protected internal string VersionedTableName
		{
			get { return GetTableName(0); }
		}
		protected internal bool[] SubclassColumnLaziness
		{
			get { return subclassColumnLazyClosure; }
		}
		protected internal bool[] SubclassFormulaLaziness
		{
			get { return subclassFormulaLazyClosure; }
		}
		/// <summary> 
		/// We can't immediately add to the cache if we have formulas
		/// which must be evaluated, or if we have the possibility of
		/// two concurrent updates to the same item being merged on
		/// the database. This can happen if (a) the item is not
		/// versioned and either (b) we have dynamic update enabled
		/// or (c) we have multiple tables holding the state of the
		/// item.
		/// </summary>
		public bool IsCacheInvalidationRequired
		{
			get { return HasFormulaProperties || (!IsVersioned && (entityMetamodel.IsDynamicUpdate || TableSpan > 1)); }
		}
		public bool IsLazyPropertiesCacheable
		{
			get { return isLazyPropertiesCacheable; }
		}
		public virtual string RootTableName
		{
			get { return GetSubclassTableName(0); }
		}
		public virtual string[] RootTableIdentifierColumnNames
		{
			get { return RootTableKeyColumnNames; }
		}
		protected internal string[] PropertySubclassNames
		{
			get { return propertySubclassNames; }
		}
		protected string[][] SubclassPropertyFormulaTemplateClosure
		{
			get { return subclassPropertyFormulaTemplateClosure; }
		}
		protected IType[] SubclassPropertyTypeClosure
		{
			get { return subclassPropertyTypeClosure; }
		}
		protected string[][] SubclassPropertyColumnNameClosure
		{
			get { return subclassPropertyColumnNameClosure; }
		}
		protected string[] SubclassPropertyNameClosure
		{
			get { return subclassPropertyNameClosure; }
		}
		protected string[] SubclassPropertySubclassNameClosure
		{
			get { return subclassPropertySubclassNameClosure; }
		}
		protected string[] SubclassColumnClosure
		{
			get { return subclassColumnClosure; }
		}
		protected string[] SubclassColumnAliasClosure
		{
			get { return subclassColumnAliasClosure; }
		}
		protected string[] SubclassFormulaClosure
		{
			get { return subclassFormulaClosure; }
		}
		protected string[] SubclassFormulaTemplateClosure
		{
			get { return subclassFormulaTemplateClosure; }
		}
		protected string[] SubclassFormulaAliasClosure
		{
			get { return subclassFormulaAliasClosure; }
		}
		public string IdentitySelectString
		{
			get
			{
				if (identitySelectString == null)
					identitySelectString =
						Factory.Dialect.GetIdentitySelectString(GetTableName(0), GetKeyColumns(0)[0],
																										IdentifierType.SqlTypes(Factory)[0].DbType);
				return identitySelectString;
			}
		}
		private string RootAlias
		{
			get { return StringHelper.GenerateAlias(EntityName); }
		}
		public ISessionFactoryImplementor Factory
		{
			get { return factory; }
		}
		public EntityMetamodel EntityMetamodel
		{
			get { return entityMetamodel; }
		}
		public ICacheConcurrencyStrategy Cache
		{
			get { return cache; }
		}
		public ICacheEntryStructure CacheEntryStructure
		{
			get { return cacheEntryStructure; }
		}
		public IComparer VersionComparator
		{
			get { return IsVersioned ? VersionType.Comparator : null; }
		}
		public string EntityName
		{
			get { return entityMetamodel.Name; }
		}
		public EntityType EntityType
		{
			get { return entityMetamodel.EntityType; }
		}
		public virtual bool IsPolymorphic
		{
			get { return entityMetamodel.IsPolymorphic; }
		}
		public virtual bool IsInherited
		{
			get { return entityMetamodel.IsInherited; }
		}
		public virtual IVersionType VersionType
		{
			get { return LocateVersionType(); }
		}
		public virtual int VersionProperty
		{
			get { return entityMetamodel.VersionPropertyIndex; }
		}
		public virtual bool IsVersioned
		{
			get { return entityMetamodel.IsVersioned; }
		}
		public virtual bool IsIdentifierAssignedByInsert
		{
			get { return entityMetamodel.IdentifierProperty.IsIdentifierAssignedByInsert; }
		}
		public virtual bool IsMutable
		{
			get { return entityMetamodel.IsMutable; }
		}
		public virtual bool IsAbstract
		{
			get { return entityMetamodel.IsAbstract; }
		}
		public virtual IIdentifierGenerator IdentifierGenerator
		{
			get { return entityMetamodel.IdentifierProperty.IdentifierGenerator; }
		}
		public virtual string RootEntityName
		{
			get { return entityMetamodel.RootName; }
		}
		public virtual IClassMetadata ClassMetadata
		{
			get { return this; }
		}
		public virtual string MappedSuperclass
		{
			get { return entityMetamodel.Superclass; }
		}
		public virtual bool IsExplicitPolymorphism
		{
			get { return entityMetamodel.IsExplicitPolymorphism; }
		}
		public string[] KeyColumnNames
		{
			get { return IdentifierColumnNames; }
		}
		public string[] JoinColumnNames
		{
			get { return KeyColumnNames; }
		}
		public string Name
		{
			get { return EntityName; }
		}
		public bool IsCollection
		{
			get { return false; }
		}
		public IType Type
		{
			get { return entityMetamodel.EntityType; }
		}
		public bool IsSelectBeforeUpdateRequired
		{
			get { return entityMetamodel.IsSelectBeforeUpdate; }
		}
		public bool IsVersionPropertyGenerated
		{
			get { return IsVersioned && PropertyUpdateGenerationInclusions[VersionProperty] != ValueInclusion.None; }
		}
		public bool VersionPropertyInsertable
		{
			get { return IsVersioned && PropertyInsertability[VersionProperty]; }
		}
		public virtual string[] PropertyNames
		{
			get { return entityMetamodel.PropertyNames; }
		}
		public virtual IType[] PropertyTypes
		{
			get { return entityMetamodel.PropertyTypes; }
		}
		public bool[] PropertyLaziness
		{
			get { return entityMetamodel.PropertyLaziness; }
		}
		public virtual bool[] PropertyCheckability
		{
			get { return entityMetamodel.PropertyCheckability; }
		}
		public bool[] NonLazyPropertyUpdateability
		{
			get { return entityMetamodel.NonlazyPropertyUpdateability; }
		}
		public virtual bool[] PropertyInsertability
		{
			get { return entityMetamodel.PropertyInsertability; }
		}
		public ValueInclusion[] PropertyInsertGenerationInclusions
		{
			get { return entityMetamodel.PropertyInsertGenerationInclusions; }
		}
		public ValueInclusion[] PropertyUpdateGenerationInclusions
		{
			get { return entityMetamodel.PropertyUpdateGenerationInclusions; }
		}
		public virtual bool[] PropertyNullability
		{
			get { return entityMetamodel.PropertyNullability; }
		}
		public virtual bool[] PropertyVersionability
		{
			get { return entityMetamodel.PropertyVersionability; }
		}
		public virtual CascadeStyle[] PropertyCascadeStyles
		{
			get { return entityMetamodel.CascadeStyles; }
		}
		public virtual bool IsMultiTable
		{
			get { return false; }
		}
		public string TemporaryIdTableName
		{
			get { return temporaryIdTableName; }
		}
		public string TemporaryIdTableDDL
		{
			get { return temporaryIdTableDDL; }
		}
		protected int PropertySpan
		{
			get { return entityMetamodel.PropertySpan; }
		}
		public virtual string IdentifierPropertyName
		{
			get { return entityMetamodel.IdentifierProperty.Name; }
		}
		public virtual IType GetIdentifierType(int j)
		{
			return IdentifierType;
		}
		public virtual IType IdentifierType
		{
			get { return entityMetamodel.IdentifierProperty.Type; }
		}
		public int[] NaturalIdentifierProperties
		{
			get { return entityMetamodel.NaturalIdentifierProperties; }
		}
		public abstract string[][] ConstraintOrderedTableKeyColumnClosure { get;}
		public abstract IType DiscriminatorType { get;}
		public abstract string[] ConstraintOrderedTableNameClosure { get;}
		public abstract string DiscriminatorSQLValue { get;}
		public abstract object DiscriminatorValue { get;}
		public abstract string[] SubclassClosure { get; }
		public abstract string[] PropertySpaces { get;}
		protected virtual void AddDiscriminatorToInsert(SqlInsertBuilder insert) { }
		protected virtual void AddDiscriminatorToSelect(SelectFragment select, string name, string suffix) { }
		public abstract string GetSubclassTableName(int j);
		//gets the identifier for a join table if other than pk
		protected virtual object GetJoinTableId(int j, object[] fields)
		{
			return null;
		}
		protected virtual object GetJoinTableId(int table, object obj)
		{
			return null;
		}
		//for joining to other keys than pk
		protected virtual string[] GetJoinIdKeyColumns(int j)
		{
			return IdentifierColumnNames;
		}
		protected abstract string[] GetSubclassTableKeyColumns(int j);
		protected abstract bool IsClassOrSuperclassTable(int j);
		protected abstract int SubclassTableSpan { get; }
		protected abstract int TableSpan { get; }
		protected abstract bool IsTableCascadeDeleteEnabled(int j);
		protected abstract string GetTableName(int table);
		protected abstract string[] GetKeyColumns(int table);
		protected abstract bool IsPropertyOfTable(int property, int table);
		protected virtual int? GetRefIdColumnOfTable(int table)
		{
			return null;
		}
		protected virtual Tuple.Property GetIdentiferProperty(int table)
		{
			var refId = GetRefIdColumnOfTable(table);
			if (refId == null)
				return entityMetamodel.IdentifierProperty;
			return entityMetamodel.Properties[refId.Value];
		}
		protected virtual bool IsIdOfTable(int property, int table)
		{
			return false;
		}
		protected abstract int GetSubclassPropertyTableNumber(int i);
		public abstract string FilterFragment(string alias);
		protected internal virtual string DiscriminatorAlias
		{
			get { return Discriminator_Alias; }
		}
		protected virtual bool IsInverseTable(int j)
		{
			return false;
		}
		protected virtual bool IsNullableTable(int j)
		{
			return false;
		}
		protected virtual bool IsNullableSubclassTable(int j)
		{
			return false;
		}
		protected virtual bool IsInverseSubclassTable(int j)
		{
			return false;
		}
		public virtual bool IsSubclassEntityName(string entityName)
		{
			return entityMetamodel.SubclassEntityNames.Contains(entityName);
		}
		protected bool[] TableHasColumns
		{
			get { return tableHasColumns; }
		}
		protected bool IsInsertCallable(int j)
		{
			return insertCallable[j];
		}
		protected bool IsUpdateCallable(int j)
		{
			return updateCallable[j];
		}
		protected bool IsDeleteCallable(int j)
		{
			return deleteCallable[j];
		}
		protected virtual bool IsSubclassPropertyDeferred(string propertyName, string entityName)
		{
			return false;
		}
		protected virtual bool IsSubclassTableSequentialSelect(int table)
		{
			return false;
		}
		public virtual bool HasSequentialSelect
		{
			get { return false; }
		}
		/// <summary>
		/// Decide which tables need to be updated
		/// </summary>
		/// <param name="dirtyProperties">The indices of all the entity properties considered dirty.</param>
		/// <param name="hasDirtyCollection">Whether any collections owned by the entity which were considered dirty. </param>
		/// <returns> Array of booleans indicating which table require updating. </returns>
		/// <remarks>
		/// The return here is an array of boolean values with each index corresponding
		/// to a given table in the scope of this persister.
		/// </remarks>
		protected virtual bool[] GetTableUpdateNeeded(int[] dirtyProperties, bool hasDirtyCollection)
		{
			if (dirtyProperties == null)
			{
				return TableHasColumns; //for object that came in via update()
			}
			else
			{
				bool[] updateability = PropertyUpdateability;
				int[] propertyTableNumbers = PropertyTableNumbers;
				bool[] tableUpdateNeeded = new bool[TableSpan];
				for (int i = 0; i < dirtyProperties.Length; i++)
				{
					int property = dirtyProperties[i];
					int table = propertyTableNumbers[property];
					tableUpdateNeeded[table] = tableUpdateNeeded[table] ||
																		 (GetPropertyColumnSpan(property) > 0 && updateability[property]);
				}
				if (IsVersioned)
				{
					// NH-2386 when there isn't dirty-properties and the version is generated even in UPDATE
					// we can't execute an UPDATE because there isn't something to UPDATE
					if(!entityMetamodel.VersionProperty.IsUpdateGenerated)
					{
						tableUpdateNeeded[0] = tableUpdateNeeded[0] ||
																	 Versioning.IsVersionIncrementRequired(dirtyProperties, hasDirtyCollection,
																																				 PropertyVersionability);
					}
				}
				return tableUpdateNeeded;
			}
		}
		public virtual bool HasRowId
		{
			get { return rowIdName != null; }
		}
		protected internal virtual SqlString GenerateLazySelectString()
		{
			if (!entityMetamodel.HasLazyProperties)
				return null;
			HashSet<int> tableNumbers = new HashSet<int>();
			List<int> columnNumbers = new List<int>();
			List<int> formulaNumbers = new List<int>();
			for (int i = 0; i < lazyPropertyNames.Length; i++)
			{
				// all this only really needs to consider properties
				// of this class, not its subclasses, but since we
				// are reusing code used for sequential selects, we
				// use the subclass closure
				int propertyNumber = GetSubclassPropertyIndex(lazyPropertyNames[i]);
				int tableNumber = GetSubclassPropertyTableNumber(propertyNumber);
				tableNumbers.Add(tableNumber);
				int[] colNumbers = subclassPropertyColumnNumberClosure[propertyNumber];
				for (int j = 0; j < colNumbers.Length; j++)
				{
					if (colNumbers[j] != -1)
					{
						columnNumbers.Add(colNumbers[j]);
					}
				}
				int[] formNumbers = subclassPropertyFormulaNumberClosure[propertyNumber];
				for (int j = 0; j < formNumbers.Length; j++)
				{
					if (formNumbers[j] != -1)
					{
						formulaNumbers.Add(formNumbers[j]);
					}
				}
			}
			if (columnNumbers.Count == 0 && formulaNumbers.Count == 0)
			{
				// only one-to-one is lazy fetched
				return null;
			}
			return RenderSelect(tableNumbers.ToArray(), columnNumbers.ToArray(), formulaNumbers.ToArray());
		}
		public virtual object InitializeLazyProperty(string fieldName, object entity, ISessionImplementor session)
		{
			object id = session.GetContextEntityIdentifier(entity);
			EntityEntry entry = session.PersistenceContext.GetEntry(entity);
			if (entry == null)
				throw new HibernateException("entity is not associated with the session: " + id);
			if (log.IsDebugEnabled)
			{
				log.Debug(
					string.Format("initializing lazy properties of: {0}, field access: {1}",
												MessageHelper.InfoString(this, id, Factory), fieldName));
			}
			if (HasCache && session.CacheMode.HasFlag(CacheMode.Get))
			{
				CacheKey cacheKey = session.GenerateCacheKey(id, IdentifierType, EntityName);
				object ce = Cache.Get(cacheKey, session.Timestamp);
				if (ce != null)
				{
					CacheEntry cacheEntry = (CacheEntry)CacheEntryStructure.Destructure(ce, factory);
					if (!cacheEntry.AreLazyPropertiesUnfetched)
					{
						//note early exit here:
						return InitializeLazyPropertiesFromCache(fieldName, entity, session, entry, cacheEntry);
					}
				}
			}
			return InitializeLazyPropertiesFromDatastore(fieldName, entity, session, id, entry);
		}
		private object InitializeLazyPropertiesFromDatastore(string fieldName, object entity, ISessionImplementor session, object id, EntityEntry entry)
		{
			if (!HasLazyProperties)
				throw new AssertionFailure("no lazy properties");
			log.Debug("initializing lazy properties from datastore");
			using (new SessionIdLoggingContext(session.SessionId)) 
			try
			{
				object result = null;
				DbCommand ps = null;
				DbDataReader rs = null;
				try
				{
					SqlString lazySelect = SQLLazySelectString;
					if (lazySelect != null)
					{
						// null sql means that the only lazy properties
						// are shared PK one-to-one associations which are
						// handled differently in the Type#nullSafeGet code...
						ps = session.Batcher.PrepareCommand(CommandType.Text, lazySelect, IdentifierType.SqlTypes(Factory));
						IdentifierType.NullSafeSet(ps, id, 0, session);
						rs = session.Batcher.ExecuteReader(ps);
						rs.Read();
					}
					object[] snapshot = entry.LoadedState;
					for (int j = 0; j < lazyPropertyNames.Length; j++)
					{
						object propValue = lazyPropertyTypes[j].NullSafeGet(rs, lazyPropertyColumnAliases[j], session, entity);
						if (InitializeLazyProperty(fieldName, entity, session, snapshot, j, propValue))
						{
							result = propValue;
						}
					}
				}
				finally
				{
					session.Batcher.CloseCommand(ps, rs);
				}
				log.Debug("done initializing lazy properties");
				return result;
			}
			catch (DbException sqle)
			{
				var exceptionContext = new AdoExceptionContextInfo
										{
											SqlException = sqle,
											Message =
												"could not initialize lazy properties: " + MessageHelper.InfoString(this, id, Factory),
											Sql = SQLLazySelectString.ToString(),
											EntityName = EntityName,
											EntityId = id
										};
				throw ADOExceptionHelper.Convert(Factory.SQLExceptionConverter, exceptionContext);
			}
		}
		private object InitializeLazyPropertiesFromCache(string fieldName, object entity, ISessionImplementor session, EntityEntry entry, CacheEntry cacheEntry)
		{
			log.Debug("initializing lazy properties from second-level cache");
			object result = null;
			object[] disassembledValues = cacheEntry.DisassembledState;
			object[] snapshot = entry.LoadedState;


Answer: 			for (int j = 0; j < lazyPropertyNames.Length; j++)