//------------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// Microsoft
// Microsoft
//------------------------------------------------------------------------------
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Data.Objects.Internal;
namespace System.Data.Common.Internal.Materialization
{
///
/// An immutable class used to generate new coordinators. These coordinators are used
/// at runtime to materialize results.
///
internal abstract class CoordinatorFactory
{
#region statics
///
/// Function of shaper that returns true; one default case when there is no explicit predicate.
///
private static readonly Func AlwaysTrue = s => true;
///
/// Function of shaper that returns false; one default case used when there is no explicit predicate.
///
private static readonly Func AlwaysFalse = s => false;
#endregion
#region state
///
/// Gets depth of the reader (0 is top-level -- which incidentally doesn't
/// require a coordinator...
///
internal readonly int Depth;
///
/// Indicates which state slot in the Shaper.State is expected to hold the
/// value for this nested reader result.
///
internal readonly int StateSlot;
///
/// A function determining whether the current row has data for this nested result.
///
internal readonly Func HasData;
///
/// A function setting key values. (the return value is irrelevant)
///
internal readonly Func SetKeys;
///
/// A function returning true if key values match the previously set values.
///
internal readonly Func CheckKeys;
///
/// Nested results below this (at depth + 1)
///
internal readonly System.Collections.ObjectModel.ReadOnlyCollection NestedCoordinators;
///
/// Indicates whether this is a leaf reader.
///
internal readonly bool IsLeafResult;
///
/// Indicates whether this coordinator can be managed by a simple enumerator. A simple enumerator
/// returns a single element per row, so the following conditions disqualify the enumerator:
/// nested collections, data discriminators (not all rows have data), keys (not all rows have new data).
///
internal readonly bool IsSimple;
///
/// For value-layer queries, the factories for all the records that we can potentially process
/// at this level in the query result.
///
internal readonly System.Collections.ObjectModel.ReadOnlyCollection RecordStateFactories;
#endregion
#region constructor
protected CoordinatorFactory(int depth, int stateSlot, Func hasData, Func setKeys, Func checkKeys, CoordinatorFactory[] nestedCoordinators, RecordStateFactory[] recordStateFactories)
{
this.Depth = depth;
this.StateSlot = stateSlot;
// figure out if there are any nested coordinators
this.IsLeafResult = 0 == nestedCoordinators.Length;
// if there is no explicit 'has data' discriminator, it means all rows contain data for the coordinator
if (hasData == null)
{
this.HasData = AlwaysTrue;
}
else
{
this.HasData = hasData;
}
// if there is no explicit set key delegate, just return true (the value is not used anyways)
if (setKeys == null)
{
this.SetKeys = AlwaysTrue;
}
else
{
this.SetKeys = setKeys;
}
// If there are no keys, it means different things depending on whether we are a leaf
// coordinator or an inner (or 'driving') coordinator. For a leaf coordinator, it means
// that every row is a new result. For an inner coordinator, it means that there is no
// key to check. This should only occur where there is a SingleRowTable (in other words,
// all rows are elements of a single child collection).
if (checkKeys == null)
{
if (this.IsLeafResult)
{
this.CheckKeys = AlwaysFalse; // every row is a new result (the keys don't match)
}
else
{
this.CheckKeys = AlwaysTrue; // every row belongs to a single child collection
}
}
else
{
this.CheckKeys = checkKeys;
}
this.NestedCoordinators = new System.Collections.ObjectModel.ReadOnlyCollection(nestedCoordinators);
this.RecordStateFactories = new System.Collections.ObjectModel.ReadOnlyCollection(recordStateFactories);
// Determines whether this coordinator can be handled by a 'simple' enumerator. See IsSimple for details.
this.IsSimple = IsLeafResult && null == checkKeys && null == hasData;
}
#endregion
#region "public" surface area
///
/// Creates a buffer handling state needed by this coordinator.
///
internal abstract Coordinator CreateCoordinator(Coordinator parent, Coordinator next);
#endregion
}
///
/// Typed
///
internal sealed class CoordinatorFactory : CoordinatorFactory
{
#region state
///
/// Reads a single element of the result from the given reader state object, returning the
/// result as a wrapped entity. May be null if the element is not available as a wrapped entity.
///
internal readonly Func WrappedElement;
///
/// Reads a single element of the result from the given reader state object.
/// May be null if the element is available as a wrapped entity instead.
///
internal readonly Func Element;
///
/// Same as Element but uses slower patterns to provide better exception messages (e.g.
/// using reader.GetValue + type check rather than reader.GetInt32)
///
internal readonly Func ElementWithErrorHandling;
///
/// Initializes the collection storing results from this coordinator.
///
internal readonly Func> InitializeCollection;
///
/// Description of this CoordinatorFactory, used for debugging only; while this is not
/// needed in retail code, it is pretty important because it's the only description we'll
/// have once we compile the Expressions; debugging a problem with retail bits would be
/// pretty hard without this.
///
private readonly string Description;
#endregion
#region constructor
public CoordinatorFactory(int depth, int stateSlot, Expression hasData, Expression setKeys, Expression checkKeys, CoordinatorFactory[] nestedCoordinators, Expression element, Expression elementWithErrorHandling, Expression initializeCollection, RecordStateFactory[] recordStateFactories)
: base(depth, stateSlot, CompilePredicate(hasData), CompilePredicate(setKeys), CompilePredicate(checkKeys), nestedCoordinators, recordStateFactories)
{
// If we are in a case where a wrapped entity is available, then use it; otherwise use the raw element.
// However, in both cases, use the raw element for the error handling case where what we care about is
// getting the appropriate exception message.
if (typeof(IEntityWrapper).IsAssignableFrom(element.Type))
{
this.WrappedElement = Translator.Compile(element);
elementWithErrorHandling = Translator.Emit_UnwrapAndEnsureType(elementWithErrorHandling, typeof(TElement));
}
else
{
this.Element = Translator.Compile(element);
}
this.ElementWithErrorHandling = Translator.Compile(elementWithErrorHandling);
this.InitializeCollection = null == initializeCollection
? s => new List()
: Translator.Compile>(initializeCollection);
this.Description = new StringBuilder()
.Append("HasData: ")
.AppendLine(DescribeExpression(hasData))
.Append("SetKeys: ")
.AppendLine(DescribeExpression(setKeys))
.Append("CheckKeys: ")
.AppendLine(DescribeExpression(checkKeys))
.Append("Element: ")
.AppendLine(DescribeExpression(element))
.Append("ElementWithExceptionHandling: ")
.AppendLine(DescribeExpression(elementWithErrorHandling))
.Append("InitializeCollection: ")
.AppendLine(DescribeExpression(initializeCollection))
.ToString();
}
#endregion
#region expression helpers
///
/// Return the compiled expression for the predicate
///
private static Func CompilePredicate(Expression predicate)
{
Func result;
if (null == predicate)
{
result = null;
}
else
{
result = Translator.Compile(predicate);
}
return result;
}
///
/// Returns a string representation of the expression
///
private static string DescribeExpression(Expression expression)
{
string result;
if (null == expression)
{
result = "undefined";
}
else
{
result = expression.ToString();
}
return result;
}
#endregion
#region "public" surface area
///
/// Create a coordinator used for materialization of collections. Unlike the CoordinatorFactory,
/// the Coordinator contains mutable state.
///
internal override Coordinator CreateCoordinator(Coordinator parent, Coordinator next)
{
return new Coordinator(this, parent, next);
}
///
/// Returns the "default" record state (that is, the one we use for PreRead/PastEnd reader states
///
internal RecordState GetDefaultRecordState(Shaper shaper)
{
RecordState result = null;
if (this.RecordStateFactories.Count > 0)
{
//
result = (RecordState)shaper.State[this.RecordStateFactories[0].StateSlotNumber];
Debug.Assert(null != result, "did you initialize the record states?");
result.ResetToDefaultState();
}
return result;
}
public override string ToString()
{
return Description;
}
#endregion
}
}