//---------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
//
// @owner Microsoft
// @backupOwner Microsoft
//---------------------------------------------------------------------
namespace System.Data.Common.EntitySql
{
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.Metadata.Edm;
using System.Diagnostics;
using System.Linq;
///
/// Represents function overload resolution mechanism, used by L2E and eSQL frontends.
///
internal static class FunctionOverloadResolver
{
///
/// Resolves against the list of function signatures.
///
/// Funciton metadata
internal static EdmFunction ResolveFunctionOverloads(IList functionsMetadata,
IList argTypes,
bool isGroupAggregateFunction,
out bool isAmbiguous)
{
return ResolveFunctionOverloads(
functionsMetadata,
argTypes,
(edmFunction) => edmFunction.Parameters,
(functionParameter) => functionParameter.TypeUsage,
(functionParameter) => functionParameter.Mode,
(argType) => TypeSemantics.FlattenType(argType),
(paramType, argType) => TypeSemantics.FlattenType(paramType),
(fromType, toType) => TypeSemantics.IsPromotableTo(fromType, toType),
(fromType, toType) => TypeSemantics.IsStructurallyEqual(fromType, toType),
isGroupAggregateFunction,
out isAmbiguous);
}
///
/// Resolves against the list of function signatures.
///
/// Funciton metadata
internal static EdmFunction ResolveFunctionOverloads(IList functionsMetadata,
IList argTypes,
Func> flattenArgumentType,
Func> flattenParameterType,
Func isPromotableTo,
Func isStructurallyEqual,
bool isGroupAggregateFunction,
out bool isAmbiguous)
{
return ResolveFunctionOverloads(
functionsMetadata,
argTypes,
(edmFunction) => edmFunction.Parameters,
(functionParameter) => functionParameter.TypeUsage,
(functionParameter) => functionParameter.Mode,
flattenArgumentType,
flattenParameterType,
isPromotableTo,
isStructurallyEqual,
isGroupAggregateFunction,
out isAmbiguous);
}
///
/// Resolves against the list of function signatures.
///
/// function formal signature getter
/// TypeUsage getter for a signature param
/// ParameterMode getter for a signature param
/// Funciton metadata
internal static TFunctionMetadata ResolveFunctionOverloads(
IList functionsMetadata,
IList argTypes,
Func> getSignatureParams,
Func getParameterTypeUsage,
Func getParameterMode,
Func> flattenArgumentType,
Func> flattenParameterType,
Func isPromotableTo,
Func isStructurallyEqual,
bool isGroupAggregateFunction,
out bool isAmbiguous) where TFunctionMetadata : class
{
//
// Flatten argument list
//
List argTypesFlat = new List(argTypes.Count);
foreach (TypeUsage argType in argTypes)
{
argTypesFlat.AddRange(flattenArgumentType(argType));
}
//
// Find a candidate overload with the best total rank, remember the candidate and its composite rank.
//
TFunctionMetadata bestCandidate = null;
isAmbiguous = false;
List ranks = new List(functionsMetadata.Count);
int[] bestCandidateRank = null;
for (int i = 0, maxTotalRank = int.MinValue; i < functionsMetadata.Count; i++)
{
int totalRank;
int[] rank;
if (TryRankFunctionParameters(argTypes,
argTypesFlat,
getSignatureParams(functionsMetadata[i]),
getParameterTypeUsage,
getParameterMode,
flattenParameterType,
isPromotableTo,
isStructurallyEqual,
isGroupAggregateFunction,
out totalRank, out rank))
{
if (totalRank == maxTotalRank)
{
isAmbiguous = true;
}
else if (totalRank > maxTotalRank)
{
isAmbiguous = false;
maxTotalRank = totalRank;
bestCandidate = functionsMetadata[i];
bestCandidateRank = rank;
}
Debug.Assert(argTypesFlat.Count == rank.Length, "argTypesFlat.Count == rank.Length");
ranks.Add(rank);
}
}
//
// If there is a best candidate, check it for ambiguity against composite ranks of other candidates
//
if (bestCandidate != null &&
!isAmbiguous &&
argTypesFlat.Count > 1 && // best candidate may be ambiguous only in the case of 2 or more arguments
ranks.Count > 1)
{
Debug.Assert(bestCandidateRank != null);
//
// Search collection of composite ranks to see if there is an overload that would render the best candidate ambiguous
//
isAmbiguous = ranks.Any(rank =>
{
Debug.Assert(rank.Length == bestCandidateRank.Length, "composite ranks have different number of elements");
if (!Object.ReferenceEquals(bestCandidateRank, rank)) // do not compare best cadnidate against itself
{
// All individual ranks of the best candidate must equal or better than the ranks of all other candidates,
// otherwise we consider it ambigous, even though it has an unambigously best total rank.
for (int i = 0; i < rank.Length; ++i)
{
if (bestCandidateRank[i] < rank[i])
{
return true;
}
}
}
return false;
});
}
return isAmbiguous ? null : bestCandidate;
}
///
/// Check promotability, returns true if argument list is promotable to the overload and overload was successfully ranked, otherwise false.
/// Ranks the overload parameter types against the argument list.
///
/// list of argument types
/// flattened list of argument types
/// list of overload parameter types
/// TypeUsage getter for the overload parameters
/// ParameterMode getter for the overload parameters
/// returns total promotion rank of the overload, 0 if no arguments
/// returns individual promotion ranks of the overload parameters, empty array if no arguments
private static bool TryRankFunctionParameters(IList argumentList,
IList flatArgumentList,
IList overloadParamList,
Func getParameterTypeUsage,
Func getParameterMode,
Func> flattenParameterType,
Func isPromotableTo,
Func isStructurallyEqual,
bool isGroupAggregateFunction,
out int totalRank,
out int[] parameterRanks)
{
totalRank = 0;
parameterRanks = null;
if (argumentList.Count != overloadParamList.Count)
{
return false;
}
//
// Check promotability and flatten the parameter types
//
List flatOverloadParamList = new List(flatArgumentList.Count);
for (int i = 0; i < overloadParamList.Count; ++i)
{
TypeUsage argumentType = argumentList[i];
TypeUsage parameterType = getParameterTypeUsage(overloadParamList[i]);
//
// Parameter mode must match.
//
ParameterMode parameterMode = getParameterMode(overloadParamList[i]);
if (parameterMode != ParameterMode.In && parameterMode != ParameterMode.InOut)
{
return false;
}
//
// If function being ranked is a group aggregate, consider the element type.
//
if (isGroupAggregateFunction)
{
if (!TypeSemantics.IsCollectionType(parameterType))
{
//
// Even though it is the job of metadata to ensure that the provider manifest is consistent.
// Ensure that if a function is marked as aggregate, then the argument type must be of collection{GivenType}.
//
throw EntityUtil.EntitySqlError(Strings.InvalidArgumentTypeForAggregateFunction);
}
parameterType = TypeHelpers.GetElementTypeUsage(parameterType);
}
//
// If argument is not promotable - reject the overload.
//
if (!isPromotableTo(argumentType, parameterType))
{
return false;
}
//
// Flatten the parameter type.
//
flatOverloadParamList.AddRange(flattenParameterType(parameterType, argumentType));
}
Debug.Assert(flatArgumentList.Count == flatOverloadParamList.Count, "flatArgumentList.Count == flatOverloadParamList.Count");
//
// Rank argument promotions
//
parameterRanks = new int[flatOverloadParamList.Count];
for (int i = 0; i < parameterRanks.Length; ++i)
{
int rank = GetPromotionRank(flatArgumentList[i], flatOverloadParamList[i], isPromotableTo, isStructurallyEqual);
totalRank += rank;
parameterRanks[i] = rank;
}
return true;
}
///
/// Ranks the -> promotion.
/// Range of values: 0 to negative infinity, with 0 as the best rank (promotion to self).
/// must be promotable to , otherwise internal error is thrown.
///
private static int GetPromotionRank(TypeUsage fromType,
TypeUsage toType,
Func isPromotableTo,
Func isStructurallyEqual)
{
//
// Only promotable types are allowed at this point.
//
Debug.Assert(isPromotableTo(fromType, toType), "isPromotableTo(fromType, toType)");
//
// If both types are the same return rank 0 - the best match.
//
if (isStructurallyEqual(fromType, toType))
{
return 0;
}
//
// In the case of eSQL untyped null will float up to the point of isStructurallyEqual(...) above.
// Below it eveything should be normal.
//
Debug.Assert(fromType != null, "fromType != null");
Debug.Assert(toType != null, "toType != null");
//
// Handle primitive types
//
PrimitiveType primitiveFromType = fromType.EdmType as PrimitiveType;
PrimitiveType primitiveToType = toType.EdmType as PrimitiveType;
if (primitiveFromType != null && primitiveToType != null)
{
if (Helper.AreSameSpatialUnionType(primitiveFromType, primitiveToType))
{
return 0;
}
IList promotions = EdmProviderManifest.Instance.GetPromotionTypes(primitiveFromType);
int promotionIndex = promotions.IndexOf(primitiveToType);
if (promotionIndex < 0)
{
throw EntityUtil.InternalError(EntityUtil.InternalErrorCode.FailedToGeneratePromotionRank, 1);
}
return -promotionIndex;
}
//
// Handle entity/relship types
//
EntityTypeBase entityBaseFromType = fromType.EdmType as EntityTypeBase;
EntityTypeBase entityBaseToType = toType.EdmType as EntityTypeBase;
if (entityBaseFromType != null && entityBaseToType != null)
{
int promotionIndex = 0;
EdmType t;
for (t = entityBaseFromType; t != entityBaseToType && t != null; t = t.BaseType, ++promotionIndex);
if (t == null)
{
throw EntityUtil.InternalError(EntityUtil.InternalErrorCode.FailedToGeneratePromotionRank, 2);
}
return -promotionIndex;
}
throw EntityUtil.InternalError(EntityUtil.InternalErrorCode.FailedToGeneratePromotionRank, 3);
}
}
}