2016-08-03 10:59:49 +00:00
//---------------------------------------------------------------------
// <copyright file="FunctionImportMappingComposable.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
2017-08-21 15:34:15 +00:00
// @owner Microsoft
2016-08-03 10:59:49 +00:00
// @backupOwner willa
//---------------------------------------------------------------------
using System.Collections.Generic ;
using System.Data.Common ;
using System.Data.Common.CommandTrees ;
using System.Data.Common.CommandTrees.ExpressionBuilder ;
using System.Data.Common.Utils ;
using System.Data.Mapping.ViewGeneration ;
using System.Data.Metadata.Edm ;
using System.Data.Query.InternalTrees ;
using System.Data.Query.PlanCompiler ;
using System.Diagnostics ;
using System.Linq ;
namespace System.Data.Mapping
{
/// <summary>
/// Represents a mapping from a model function import to a store composable function.
/// </summary>
internal class FunctionImportMappingComposable : FunctionImportMapping
{
#region Constructors
internal FunctionImportMappingComposable (
EdmFunction functionImport ,
EdmFunction targetFunction ,
List < Tuple < StructuralType , List < StorageConditionPropertyMapping > , List < StoragePropertyMapping > > > structuralTypeMappings ,
EdmProperty [ ] targetFunctionKeys ,
StorageMappingItemCollection mappingItemCollection ,
string sourceLocation ,
LineInfo lineInfo )
: base ( functionImport , targetFunction )
{
EntityUtil . CheckArgumentNull ( mappingItemCollection , "mappingItemCollection" ) ;
Debug . Assert ( functionImport . IsComposableAttribute , "functionImport.IsComposableAttribute" ) ;
Debug . Assert ( targetFunction . IsComposableAttribute , "targetFunction.IsComposableAttribute" ) ;
Debug . Assert ( functionImport . EntitySet = = null | | structuralTypeMappings ! = null , "Function import returning entities must have structuralTypeMappings." ) ;
Debug . Assert ( structuralTypeMappings = = null | | structuralTypeMappings . Count > 0 , "Non-null structuralTypeMappings must not be empty." ) ;
EdmType resultType ;
Debug . Assert (
structuralTypeMappings ! = null | |
MetadataHelper . TryGetFunctionImportReturnType < EdmType > ( functionImport , 0 , out resultType ) & & TypeSemantics . IsScalarType ( resultType ) ,
"Either type mappings should be specified or the function import should be Collection(Scalar)." ) ;
Debug . Assert ( functionImport . EntitySet = = null | | targetFunctionKeys ! = null , "Keys must be inferred for a function import returning entities." ) ;
Debug . Assert ( targetFunctionKeys = = null | | targetFunctionKeys . Length > 0 , "Keys must be null or non-empty." ) ;
m_mappingItemCollection = mappingItemCollection ;
// We will use these parameters to target s-space function calls in the generated command tree.
// Since enums don't exist in s-space we need to use the underlying type.
m_commandParameters = functionImport . Parameters . Select ( p = > TypeHelpers . GetPrimitiveTypeUsageForScalar ( p . TypeUsage ) . Parameter ( p . Name ) ) . ToArray ( ) ;
m_structuralTypeMappings = structuralTypeMappings ;
m_targetFunctionKeys = targetFunctionKeys ;
m_sourceLocation = sourceLocation ;
m_lineInfo = lineInfo ;
}
#endregion
#region Fields
private readonly StorageMappingItemCollection m_mappingItemCollection ;
/// <summary>
/// Command parameter refs created from m_edmFunction parameters.
/// Used as arguments to target (s-space) function calls in the generated command tree.
/// </summary>
private readonly DbParameterReferenceExpression [ ] m_commandParameters ;
/// <summary>
/// Result mapping as entity type hierarchy.
/// </summary>
private readonly List < Tuple < StructuralType , List < StorageConditionPropertyMapping > , List < StoragePropertyMapping > > > m_structuralTypeMappings ;
/// <summary>
/// Keys inside the result set of the target function. Inferred based on the mapping (using c-space entity type keys).
/// </summary>
private readonly EdmProperty [ ] m_targetFunctionKeys ;
/// <summary>
/// ITree template. Requires function argument substitution during function view expansion.
/// </summary>
private Node m_internalTreeNode ;
private readonly string m_sourceLocation ;
private readonly LineInfo m_lineInfo ;
#endregion
#region Properties / Methods
internal EdmProperty [ ] TvfKeys
{
get { return m_targetFunctionKeys ; }
}
#region GetInternalTree ( . . . ) implementation
internal Node GetInternalTree ( Command targetIqtCommand , IList < Node > targetIqtArguments )
{
if ( m_internalTreeNode = = null )
{
var viewGenErrors = new List < EdmSchemaError > ( ) ;
DiscriminatorMap discriminatorMap ;
DbQueryCommandTree tree = GenerateFunctionView ( viewGenErrors , out discriminatorMap ) ;
if ( viewGenErrors . Count > 0 )
{
throw new MappingException ( Helper . CombineErrorMessage ( viewGenErrors ) ) ;
}
Debug . Assert ( tree ! = null , "tree != null" ) ;
// Convert this into an ITree first
Command itree = ITreeGenerator . Generate ( tree , discriminatorMap ) ;
var rootProject = itree . Root ; // PhysicalProject(RelInput)
PlanCompiler . Assert ( rootProject . Op . OpType = = OpType . PhysicalProject , "Expected a physical projectOp at the root of the tree - found " + rootProject . Op . OpType ) ;
var rootProjectOp = ( PhysicalProjectOp ) rootProject . Op ;
Debug . Assert ( rootProjectOp . Outputs . Count = = 1 , "rootProjectOp.Outputs.Count == 1" ) ;
var rootInput = rootProject . Child0 ; // the RelInput in PhysicalProject(RelInput)
// #554756: VarVec enumerators are not cached on the shared Command instance.
itree . DisableVarVecEnumCaching ( ) ;
// Function import returns a collection, so convert it to a scalar by wrapping into CollectOp.
Node relNode = rootInput ;
Var relVar = rootProjectOp . Outputs [ 0 ] ;
// ProjectOp does not implement Type property, so get the type from the column map.
TypeUsage functionViewType = rootProjectOp . ColumnMap . Type ;
if ( ! Command . EqualTypes ( functionViewType , this . FunctionImport . ReturnParameter . TypeUsage ) )
{
Debug . Assert ( TypeSemantics . IsPromotableTo ( functionViewType , this . FunctionImport . ReturnParameter . TypeUsage ) , "Mapping expression result type must be promotable to the c-space function return type." ) ;
// Build "relNode = Project(relNode, SoftCast(relVar))"
CollectionType expectedCollectionType = ( CollectionType ) this . FunctionImport . ReturnParameter . TypeUsage . EdmType ;
var expectedElementType = expectedCollectionType . TypeUsage ;
Node varRefNode = itree . CreateNode ( itree . CreateVarRefOp ( relVar ) ) ;
Node castNode = itree . CreateNode ( itree . CreateSoftCastOp ( expectedElementType ) , varRefNode ) ;
Node varDefListNode = itree . CreateVarDefListNode ( castNode , out relVar ) ;
ProjectOp projectOp = itree . CreateProjectOp ( relVar ) ;
relNode = itree . CreateNode ( projectOp , relNode , varDefListNode ) ;
}
// Build "Collect(PhysicalProject(relNode))
m_internalTreeNode = itree . BuildCollect ( relNode , relVar ) ;
}
Debug . Assert ( m_internalTreeNode ! = null , "m_internalTreeNode != null" ) ;
// Prepare argument replacement dictionary
Debug . Assert ( m_commandParameters . Length = = targetIqtArguments . Count , "m_commandParameters.Length == targetIqtArguments.Count" ) ;
Dictionary < string , Node > viewArguments = new Dictionary < string , Node > ( m_commandParameters . Length ) ;
for ( int i = 0 ; i < m_commandParameters . Length ; + + i )
{
var commandParam = ( DbParameterReferenceExpression ) m_commandParameters [ i ] ;
var argumentNode = targetIqtArguments [ i ] ;
// If function import parameter is of enum type, the argument value for it will be of enum type. We however have
// converted enum types to underlying types for m_commandParameters. So we now need to softcast the argument
// expression to the underlying type as well.
if ( TypeSemantics . IsEnumerationType ( argumentNode . Op . Type ) )
{
argumentNode = targetIqtCommand . CreateNode (
targetIqtCommand . CreateSoftCastOp ( TypeHelpers . CreateEnumUnderlyingTypeUsage ( argumentNode . Op . Type ) ) ,
argumentNode ) ;
}
Debug . Assert ( TypeSemantics . IsPromotableTo ( argumentNode . Op . Type , commandParam . ResultType ) , "Argument type must be promotable to parameter type." ) ;
viewArguments . Add ( commandParam . ParameterName , argumentNode ) ;
}
return FunctionViewOpCopier . Copy ( targetIqtCommand , m_internalTreeNode , viewArguments ) ;
}
private sealed class FunctionViewOpCopier : OpCopier
{
private Dictionary < string , Node > m_viewArguments ;
private FunctionViewOpCopier ( Command cmd , Dictionary < string , Node > viewArguments )
: base ( cmd )
{
m_viewArguments = viewArguments ;
}
internal static Node Copy ( Command cmd , Node viewNode , Dictionary < string , Node > viewArguments )
{
return new FunctionViewOpCopier ( cmd , viewArguments ) . CopyNode ( viewNode ) ;
}
#region Visitor Members
public override Node Visit ( VarRefOp op , Node n )
{
// The original function view has store function calls with arguments represented as command parameter refs.
// We are now replacing command parameter refs with the real argument nodes from the calling tree.
// The replacement is performed in the function view subtree and we search for parameter refs with names
// matching the FunctionImportMapping.FunctionImport parameter names (this is how the command parameters
// have been created in the first place, see m_commandParameters and GetCommandTree(...) for more info).
// The search and replace is not performed on the argument nodes themselves. This is important because it guarantees
// that we are not replacing unrelated (possibly user-defined) parameter refs that accidentally have the matching names.
Node argNode ;
if ( op . Var . VarType = = VarType . Parameter & & m_viewArguments . TryGetValue ( ( ( ParameterVar ) op . Var ) . ParameterName , out argNode ) )
{
// Just copy the argNode, do not reapply this visitor. We do not want search and replace inside the argNode. See comment above.
return OpCopier . Copy ( m_destCmd , argNode ) ;
}
else
{
return base . Visit ( op , n ) ;
}
}
#endregion
}
#endregion
#region GenerateFunctionView ( . . . ) implementation
#region GenerateFunctionView
internal DbQueryCommandTree GenerateFunctionView ( IList < EdmSchemaError > errors , out DiscriminatorMap discriminatorMap )
{
Debug . Assert ( errors ! = null , "errors != null" ) ;
discriminatorMap = null ;
// Prepare the direct call of the store function as StoreFunction(@EdmFunc_p1, ..., @EdmFunc_pN).
// Note that function call arguments are command parameters created from the m_edmFunction parameters.
Debug . Assert ( this . TargetFunction ! = null , "this.TargetFunction != null" ) ;
DbExpression storeFunctionInvoke = this . TargetFunction . Invoke ( GetParametersForTargetFunctionCall ( ) ) ;
// Generate the query expression producing c-space result from s-space function call(s).
DbExpression queryExpression ;
if ( m_structuralTypeMappings ! = null )
{
queryExpression = GenerateStructuralTypeResultMappingView ( storeFunctionInvoke , errors , out discriminatorMap ) ;
Debug . Assert ( queryExpression = = null | |
TypeSemantics . IsPromotableTo ( queryExpression . ResultType , this . FunctionImport . ReturnParameter . TypeUsage ) ,
"TypeSemantics.IsPromotableTo(queryExpression.ResultType, this.FunctionImport.ReturnParameter.TypeUsage)" ) ;
}
else
{
queryExpression = GenerateScalarResultMappingView ( storeFunctionInvoke ) ;
Debug . Assert ( queryExpression = = null | |
TypeSemantics . IsEqual ( queryExpression . ResultType , this . FunctionImport . ReturnParameter . TypeUsage ) ,
"TypeSemantics.IsEqual(queryExpression.ResultType, this.FunctionImport.ReturnParameter.TypeUsage)" ) ;
}
if ( queryExpression = = null )
{
// In case of errors during view generation, return.
return null ;
}
// Generate parameterized command, where command parameters are semantically the c-space function parameters.
return DbQueryCommandTree . FromValidExpression ( m_mappingItemCollection . Workspace , TargetPerspective . TargetPerspectiveDataSpace , queryExpression ) ;
}
private IEnumerable < DbExpression > GetParametersForTargetFunctionCall ( )
{
Debug . Assert ( this . FunctionImport . Parameters . Count = = m_commandParameters . Length , "this.FunctionImport.Parameters.Count == m_commandParameters.Length" ) ;
Debug . Assert ( this . TargetFunction . Parameters . Count = = m_commandParameters . Length , "this.TargetFunction.Parameters.Count == m_commandParameters.Length" ) ;
foreach ( var targetParameter in this . TargetFunction . Parameters )
{
Debug . Assert ( this . FunctionImport . Parameters . Contains ( targetParameter . Name ) , "this.FunctionImport.Parameters.Contains(targetParameter.Name)" ) ;
var functionImportParameter = this . FunctionImport . Parameters . Single ( p = > p . Name = = targetParameter . Name ) ;
yield return m_commandParameters [ this . FunctionImport . Parameters . IndexOf ( functionImportParameter ) ] ;
}
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] // referenced by System.Data.Entity.Design.dll
internal void ValidateFunctionView ( IList < EdmSchemaError > errors )
{
DiscriminatorMap dm ;
GenerateFunctionView ( errors , out dm ) ;
}
#endregion
#region GenerateStructuralTypeResultMappingView
private DbExpression GenerateStructuralTypeResultMappingView ( DbExpression storeFunctionInvoke , IList < EdmSchemaError > errors , out DiscriminatorMap discriminatorMap )
{
Debug . Assert ( m_structuralTypeMappings ! = null & & m_structuralTypeMappings . Count > 0 , "m_structuralTypeMappings != null && m_structuralTypeMappings.Count > 0" ) ;
discriminatorMap = null ;
// Process explicit structural type mappings. The mapping is based on the direct call of the store function
// wrapped into a projection constructing the mapped structural types.
DbExpression queryExpression = storeFunctionInvoke ;
if ( m_structuralTypeMappings . Count = = 1 )
{
var mapping = m_structuralTypeMappings [ 0 ] ;
var type = mapping . Item1 ;
var conditions = mapping . Item2 ;
var propertyMappings = mapping . Item3 ;
if ( conditions . Count > 0 )
{
queryExpression = queryExpression . Where ( ( row ) = > GenerateStructuralTypeConditionsPredicate ( conditions , row ) ) ;
}
var binding = queryExpression . BindAs ( "row" ) ;
var entityTypeMappingView = GenerateStructuralTypeMappingView ( type , propertyMappings , binding . Variable , errors ) ;
if ( entityTypeMappingView = = null )
{
return null ;
}
queryExpression = binding . Project ( entityTypeMappingView ) ;
}
else
{
var binding = queryExpression . BindAs ( "row" ) ;
// Make sure type projection is performed over a closed set where each row is guaranteed to produce a known type.
// To do this, filter the store function output using the type conditions.
Debug . Assert ( m_structuralTypeMappings . All ( m = > m . Item2 . Count > 0 ) , "In multi-type mapping each type must have conditions." ) ;
List < DbExpression > structuralTypePredicates = m_structuralTypeMappings . Select ( m = > GenerateStructuralTypeConditionsPredicate ( m . Item2 , binding . Variable ) ) . ToList ( ) ;
queryExpression = binding . Filter ( Helpers . BuildBalancedTreeInPlace (
structuralTypePredicates . ToArray ( ) , // clone, otherwise BuildBalancedTreeInPlace will change it
( prev , next ) = > prev . Or ( next ) ) ) ;
binding = queryExpression . BindAs ( "row" ) ;
List < DbExpression > structuralTypeMappingViews = new List < DbExpression > ( m_structuralTypeMappings . Count ) ;
foreach ( var mapping in m_structuralTypeMappings )
{
var type = mapping . Item1 ;
var propertyMappings = mapping . Item3 ;
var structuralTypeMappingView = GenerateStructuralTypeMappingView ( type , propertyMappings , binding . Variable , errors ) ;
if ( structuralTypeMappingView = = null )
{
continue ;
}
else
{
structuralTypeMappingViews . Add ( structuralTypeMappingView ) ;
}
}
Debug . Assert ( structuralTypeMappingViews . Count = = structuralTypePredicates . Count , "structuralTypeMappingViews.Count == structuralTypePredicates.Count" ) ;
if ( structuralTypeMappingViews . Count ! = m_structuralTypeMappings . Count )
{
Debug . Assert ( errors . Count > 0 , "errors.Count > 0" ) ;
return null ;
}
// Because we are projecting over the closed set, we can convert the last WHEN THEN into ELSE.
DbExpression typeConstructors = DbExpressionBuilder . Case (
structuralTypePredicates . Take ( m_structuralTypeMappings . Count - 1 ) ,
structuralTypeMappingViews . Take ( m_structuralTypeMappings . Count - 1 ) ,
structuralTypeMappingViews [ m_structuralTypeMappings . Count - 1 ] ) ;
queryExpression = binding . Project ( typeConstructors ) ;
if ( DiscriminatorMap . TryCreateDiscriminatorMap ( this . FunctionImport . EntitySet , queryExpression , out discriminatorMap ) )
{
Debug . Assert ( discriminatorMap ! = null , "discriminatorMap == null after it has been created" ) ;
}
}
return queryExpression ;
}
private DbExpression GenerateStructuralTypeMappingView ( StructuralType structuralType , List < StoragePropertyMapping > propertyMappings , DbExpression row , IList < EdmSchemaError > errors )
{
// Generate property views.
var properties = TypeHelpers . GetAllStructuralMembers ( structuralType ) ;
Debug . Assert ( properties . Count = = propertyMappings . Count , "properties.Count == propertyMappings.Count" ) ;
var constructorArgs = new List < DbExpression > ( properties . Count ) ;
for ( int i = 0 ; i < propertyMappings . Count ; + + i )
{
var propertyMapping = propertyMappings [ i ] ;
Debug . Assert ( properties [ i ] . EdmEquals ( propertyMapping . EdmProperty ) , "properties[i].EdmEquals(propertyMapping.EdmProperty)" ) ;
var propertyMappingView = GeneratePropertyMappingView ( propertyMapping , row , new List < string > ( ) { propertyMapping . EdmProperty . Name } , errors ) ;
if ( propertyMappingView ! = null )
{
constructorArgs . Add ( propertyMappingView ) ;
}
}
if ( constructorArgs . Count ! = propertyMappings . Count )
{
Debug . Assert ( errors . Count > 0 , "errors.Count > 0" ) ;
return null ;
}
else
{
// Return the structural type constructor.
return TypeUsage . Create ( structuralType ) . New ( constructorArgs ) ;
}
}
private DbExpression GenerateStructuralTypeConditionsPredicate ( List < StorageConditionPropertyMapping > conditions , DbExpression row )
{
Debug . Assert ( conditions . Count > 0 , "conditions.Count > 0" ) ;
DbExpression predicate = Helpers . BuildBalancedTreeInPlace ( conditions . Select ( c = > GeneratePredicate ( c , row ) ) . ToArray ( ) , ( prev , next ) = > prev . And ( next ) ) ;
return predicate ;
}
private DbExpression GeneratePredicate ( StorageConditionPropertyMapping condition , DbExpression row )
{
Debug . Assert ( condition . EdmProperty = = null , "C-side conditions are not supported in function mappings." ) ;
DbExpression columnRef = GenerateColumnRef ( row , condition . ColumnProperty ) ;
if ( condition . IsNull . HasValue )
{
return condition . IsNull . Value ? ( DbExpression ) columnRef . IsNull ( ) : ( DbExpression ) columnRef . IsNull ( ) . Not ( ) ;
}
else
{
return columnRef . Equal ( columnRef . ResultType . Constant ( condition . Value ) ) ;
}
}
private DbExpression GeneratePropertyMappingView ( StoragePropertyMapping mapping , DbExpression row , List < string > context , IList < EdmSchemaError > errors )
{
Debug . Assert ( mapping is StorageScalarPropertyMapping , "Complex property mapping is not supported in function imports." ) ;
var scalarPropertyMapping = ( StorageScalarPropertyMapping ) mapping ;
return GenerateScalarPropertyMappingView ( scalarPropertyMapping . EdmProperty , scalarPropertyMapping . ColumnProperty , row ) ;
}
private DbExpression GenerateScalarPropertyMappingView ( EdmProperty edmProperty , EdmProperty columnProperty , DbExpression row )
{
DbExpression accessorExpr = GenerateColumnRef ( row , columnProperty ) ;
if ( ! TypeSemantics . IsEqual ( accessorExpr . ResultType , edmProperty . TypeUsage ) )
{
accessorExpr = accessorExpr . CastTo ( edmProperty . TypeUsage ) ;
}
return accessorExpr ;
}
private DbExpression GenerateColumnRef ( DbExpression row , EdmProperty column )
{
Debug . Assert ( row . ResultType . EdmType . BuiltInTypeKind = = BuiltInTypeKind . RowType , "Input type is expected to be a row type." ) ;
var rowType = ( RowType ) row . ResultType . EdmType ;
Debug . Assert ( rowType . Properties . Contains ( column . Name ) , "Column name must be resolvable in the TVF result type." ) ;
return row . Property ( column . Name ) ;
}
#endregion
#region GenerateScalarResultMappingView
private DbExpression GenerateScalarResultMappingView ( DbExpression storeFunctionInvoke )
{
DbExpression queryExpression = storeFunctionInvoke ;
CollectionType functionImportReturnType ;
if ( ! MetadataHelper . TryGetFunctionImportReturnCollectionType ( this . FunctionImport , 0 , out functionImportReturnType ) )
{
Debug . Fail ( "Failed to get the result type of the function import." ) ;
}
Debug . Assert ( TypeSemantics . IsCollectionType ( queryExpression . ResultType ) , "Store function must be TVF (collection expected)." ) ;
var collectionType = ( CollectionType ) queryExpression . ResultType . EdmType ;
Debug . Assert ( TypeSemantics . IsRowType ( collectionType . TypeUsage ) , "Store function must be TVF (collection of rows expected)." ) ;
var rowType = ( RowType ) collectionType . TypeUsage . EdmType ;
var column = rowType . Properties [ 0 ] ;
Func < DbExpression , DbExpression > scalarView = ( DbExpression row ) = >
{
var propertyAccess = row . Property ( column ) ;
if ( TypeSemantics . IsEqual ( functionImportReturnType . TypeUsage , column . TypeUsage ) )
{
return propertyAccess ;
}
else
{
return propertyAccess . CastTo ( functionImportReturnType . TypeUsage ) ;
}
} ;
queryExpression = queryExpression . Select ( row = > scalarView ( row ) ) ;
return queryExpression ;
}
#endregion
#endregion
#endregion
}
}