//--------------------------------------------------------------------- // // Copyright (c) Microsoft Corporation. All rights reserved. // // // @owner [....] // @backupOwner [....] //--------------------------------------------------------------------- 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); } } }