private string BindTrim(SingleValueFunctionCallQueryNode fnNode) { // SPARQL has no trim function, so we use regular expressions with replace() var args = fnNode.Arguments.Select(BindArgument).ToList(); return("replace(" + args[0] + @", '^\s+|\s+$', '')"); // replace ws at start or end of string with empty string }
private string BindSubstringOf(SingleValueFunctionCallQueryNode fnNode) { // SPARQL equivalent is contains(str, str) with argument ordering switched var args = fnNode.Arguments.Select(BindArgument).ToList(); return("contains(" + args[1] + ", " + args[0] + ")"); }
private Expression BindTimeSpanProperty(SingleValueFunctionCallQueryNode node) { Expression[] arguments = BindArguments(node.Arguments); Contract.Assert(arguments.Length == 1 && IsDateOrOffset(arguments[0].Type)); Contract.Assert(ClrCanonicalFunctions.TimeSpanProperties.ContainsKey(node.Name)); return(MakeFunctionCall(ClrCanonicalFunctions.TimeSpanProperties[node.Name], arguments)); }
private Expression BindRound(SingleValueFunctionCallQueryNode node) { Contract.Assert("round" == node.Name); Expression[] arguments = BindArguments(node.Arguments); Contract.Assert(arguments.Length == 1 && IsDoubleOrDecimal(arguments[0].Type)); return(MakeFunctionCall(ClrCanonicalFunctions.Round, arguments)); }
private Expression BindSingleValueFunctionCallQueryNode(SingleValueFunctionCallQueryNode node) { switch (node.Name) { case "startswith": return(BindStartsWith(node)); case "endswith": return(BindEndsWith(node)); case "substringof": return(BindSubstringOf(node)); case "substring": return(BindSubstring(node)); case "length": return(BindLength(node)); case "indexof": return(BindIndexOf(node)); case "tolower": return(BindToLower(node)); case "toupper": return(BindToUpper(node)); case "trim": return(BindTrim(node)); case "concat": return(BindConcat(node)); case "year": case "month": case "day": case "hour": case "minute": case "second": return(BindDateProperty(node)); case "round": return(BindRound(node)); case "floor": return(BindFloor(node)); case "ceiling": return(BindCeiling(node)); default: throw new NotImplementedException(Error.Format(SRResources.ODataFunctionNotSupported, node.Name)); } }
private Expression BindToLower(SingleValueFunctionCallQueryNode node) { Contract.Assert("tolower" == node.Name); Expression[] arguments = BindArguments(node.Arguments); ValidateAllStringArguments(node.Name, arguments); Contract.Assert(arguments.Length == 1 && arguments[0].Type == typeof(string)); return(MakeFunctionCall(ClrCanonicalFunctions.ToLower, arguments)); }
private Expression BindEndsWith(SingleValueFunctionCallQueryNode node) { Contract.Assert("endswith" == node.Name); Expression[] arguments = BindArguments(node.Arguments); ValidateAllStringArguments(node.Name, arguments); Contract.Assert(arguments.Length == 2 && arguments[0].Type == typeof(string) && arguments[1].Type == typeof(string)); return(MakeFunctionCall(ClrCanonicalFunctions.EndsWith, arguments)); }
private Expression BindLength(SingleValueFunctionCallQueryNode node) { Contract.Assert("length" == node.Name); Expression[] arguments = BindArguments(node.Arguments); Contract.Assert(arguments.Length == 1 && arguments[0].Type == typeof(string)); Expression functionCall = Expression.Property(arguments[0], "Length"); return(CreateFunctionCallWithNullPropagation(functionCall, arguments)); }
private Expression BindSubstringOf(SingleValueFunctionCallQueryNode node) { Contract.Assert("substringof" == node.Name); Expression[] arguments = BindArguments(node.Arguments); ValidateAllStringArguments(node.Name, arguments); Contract.Assert(arguments.Length == 2 && arguments[0].Type == typeof(string) && arguments[1].Type == typeof(string)); // NOTE: this is reversed because it is reverse in WCF DS and in the OData spec return(MakeFunctionCall(ClrCanonicalFunctions.Contains, arguments[1], arguments[0])); }
private Expression BindRound(SingleValueFunctionCallQueryNode node) { Contract.Assert("round" == node.Name); Expression[] arguments = BindArguments(node.Arguments); Contract.Assert(arguments.Length == 1 && IsDoubleOrDecimal(arguments[0].Type)); Expression functionCall = Expression.Call(null, ClrCanonicalFunctions.Round, ExtractValueFromNullableArguments(arguments)); return(CreateFunctionCallWithNullPropagation(functionCall, arguments)); }
private Expression BindEndsWith(SingleValueFunctionCallQueryNode node) { Contract.Assert("endswith" == node.Name); Expression[] arguments = BindArguments(node.Arguments); Contract.Assert(arguments.Length == 2 && arguments[0].Type == typeof(string) && arguments[1].Type == typeof(string)); Expression functionCall = Expression.Call(arguments[0], ClrCanonicalFunctions.EndsWith, arguments[1]); return(CreateFunctionCallWithNullPropagation(functionCall, arguments)); }
private Expression BindSubstringOf(SingleValueFunctionCallQueryNode node) { Contract.Assert("substringof" == node.Name); Expression[] arguments = BindArguments(node.Arguments); Contract.Assert(arguments.Length == 2 && arguments[0].Type == typeof(string) && arguments[1].Type == typeof(string)); // NOTE: this is reversed because it is reverse in WCF DS and in the OData spec Expression functionCall = Expression.Call(arguments[1], ClrCanonicalFunctions.Contains, arguments[0]); return(CreateFunctionCallWithNullPropagation(functionCall, arguments)); }
private Expression BindDateProperty(SingleValueFunctionCallQueryNode node) { Contract.Assert(new string[] { "year", "month", "day", "hour", "minute", "second" }.Contains(node.Name)); Expression[] arguments = BindArguments(node.Arguments); Contract.Assert(arguments.Length == 1 && IsDateOrOffset(arguments[0].Type)); string propertyName = node.Name.Substring(0, 1).ToUpperInvariant() + node.Name.Substring(1); Expression functionCall = Expression.Property(ExtractValueFromNullableArguments(arguments)[0], propertyName); return(CreateFunctionCallWithNullPropagation(functionCall, arguments)); }
private Expression BindSubstring(SingleValueFunctionCallQueryNode node) { Contract.Assert("substring" == node.Name); Expression[] arguments = BindArguments(node.Arguments); if (arguments[0].Type != typeof(string)) { throw new ODataException(Error.Format(SRResources.FunctionNotSupportedOnEnum, node.Name)); } Expression functionCall; if (arguments.Length == 2) { Contract.Assert(IsInteger(arguments[1].Type)); // When null propagation is allowed, we use a safe version of String.Substring(int). // But for providers that would not recognize custom expressions like this, we map // directly to String.Substring(int) if (_querySettings.HandleNullPropagation == HandleNullPropagationOption.True) { // Safe function is static and takes string "this" as first argument functionCall = MakeFunctionCall(ClrCanonicalFunctions.SubstringStartNoThrow, arguments); } else { functionCall = MakeFunctionCall(ClrCanonicalFunctions.SubstringStart, arguments); } } else { // arguments.Length == 3 implies String.Substring(int, int) Contract.Assert(arguments.Length == 3 && IsInteger(arguments[1].Type) && IsInteger(arguments[2].Type)); // When null propagation is allowed, we use a safe version of String.Substring(int, int). // But for providers that would not recognize custom expressions like this, we map // directly to String.Substring(int, int) if (_querySettings.HandleNullPropagation == HandleNullPropagationOption.True) { // Safe function is static and takes string "this" as first argument functionCall = MakeFunctionCall(ClrCanonicalFunctions.SubstringStartAndLengthNoThrow, arguments); } else { functionCall = MakeFunctionCall(ClrCanonicalFunctions.SubstringStartAndLength, arguments); } } return(functionCall); }
private Expression BindDateOrDateTimeOffsetProperty(SingleValueFunctionCallQueryNode node) { Expression[] arguments = BindArguments(node.Arguments); Contract.Assert(arguments.Length == 1 && IsDateOrOffset(arguments[0].Type)); PropertyInfo property; if (IsDate(arguments[0].Type)) { Contract.Assert(ClrCanonicalFunctions.DateProperties.ContainsKey(node.Name)); property = ClrCanonicalFunctions.DateProperties[node.Name]; } else { Contract.Assert(ClrCanonicalFunctions.DateTimeOffsetProperties.ContainsKey(node.Name)); property = ClrCanonicalFunctions.DateTimeOffsetProperties[node.Name]; } return(MakeFunctionCall(ClrCanonicalFunctions.DateProperties[node.Name], arguments)); }
private Expression BindSubstring(SingleValueFunctionCallQueryNode node) { Contract.Assert("substring" == node.Name); Expression[] arguments = BindArguments(node.Arguments); Contract.Assert((arguments.Length == 2 && arguments[0].Type == typeof(string) && IsInteger(arguments[1].Type)) || (arguments.Length == 3 && arguments[0].Type == typeof(string) && IsInteger(arguments[1].Type) && IsInteger(arguments[2].Type))); Expression functionCall; if (arguments.Length == 2) { functionCall = Expression.Call(arguments[0], ClrCanonicalFunctions.SubstringStart, arguments[1]); } else { // arguments.Length == 3 functionCall = Expression.Call(arguments[0], ClrCanonicalFunctions.SubstringStartAndLength, arguments[1], arguments[2]); } return(CreateFunctionCallWithNullPropagation(functionCall, arguments)); }
private string BindSingleValueFunctionCall(SingleValueFunctionCallQueryNode fnNode) { switch (fnNode.Name.ToLowerInvariant()) { case "substringof": return BindSubstringOf(fnNode); case "endswith": return BindEndsWith(fnNode); case "startswith": return BindStartsWith(fnNode); case "length": return BindLength(fnNode); case "indexof": throw new NotSupportedException("SPARQL does not support an equivalent to OData indexof(str)"); case "substring": return BindSubstring(fnNode); case "tolower": return BindToLower(fnNode); case "toupper": return BindToUpper(fnNode); case "trim": return BindTrim(fnNode); case "concat": return BindConcat(fnNode); // Date Functions case "day": return BindDay(fnNode); case "hour": return BindHour(fnNode); case "minute": return BindMinute(fnNode); case "month": return BindMonth(fnNode); case "second": return BindSecond(fnNode); case "year": return BindYear(fnNode); // Math functions case "round": return BindRound(fnNode); case "ceiling": return BindCeiling(fnNode); case "floor": return BindFloor(fnNode); default: throw new NotImplementedException("No support for function " + fnNode.Name); } }
private string BindSubstring(SingleValueFunctionCallQueryNode fnNode) { var args = fnNode.Arguments.Select(BindArgument).ToList(); return("substr(" + String.Join(", ", args.Take(3)) + ")"); }
private string BindConcat(SingleValueFunctionCallQueryNode fnNode) { var args = fnNode.Arguments.Select(BindArgument).ToList(); return("concat(" + String.Join(", ", args) + ")"); }
private string BindFloor(SingleValueFunctionCallQueryNode fnNode) { var args = fnNode.Arguments.Select(BindArgument).ToList(); return("floor(" + args[0] + ")"); }
private Expression BindSingleValueFunctionCallQueryNode(SingleValueFunctionCallQueryNode node) { switch (node.Name) { case ClrCanonicalFunctions.StartswithFunctionName: return(BindStartsWith(node)); case ClrCanonicalFunctions.EndswithFunctionName: return(BindEndsWith(node)); case ClrCanonicalFunctions.SubstringofFunctionName: return(BindSubstringOf(node)); case ClrCanonicalFunctions.SubstringFunctionName: return(BindSubstring(node)); case ClrCanonicalFunctions.LengthFunctionName: return(BindLength(node)); case ClrCanonicalFunctions.IndexofFunctionName: return(BindIndexOf(node)); case ClrCanonicalFunctions.TolowerFunctionName: return(BindToLower(node)); case ClrCanonicalFunctions.ToupperFunctionName: return(BindToUpper(node)); case ClrCanonicalFunctions.TrimFunctionName: return(BindTrim(node)); case ClrCanonicalFunctions.ConcatFunctionName: return(BindConcat(node)); case ClrCanonicalFunctions.YearFunctionName: case ClrCanonicalFunctions.MonthFunctionName: case ClrCanonicalFunctions.DayFunctionName: case ClrCanonicalFunctions.HourFunctionName: case ClrCanonicalFunctions.MinuteFunctionName: case ClrCanonicalFunctions.SecondFunctionName: return(BindDateOrDateTimeOffsetProperty(node)); case ClrCanonicalFunctions.YearsFunctionName: case ClrCanonicalFunctions.MonthsFunctionName: case ClrCanonicalFunctions.DaysFunctionName: case ClrCanonicalFunctions.HoursFunctionName: case ClrCanonicalFunctions.MinutesFunctionName: case ClrCanonicalFunctions.SecondsFunctionName: return(BindTimeSpanProperty(node)); case ClrCanonicalFunctions.RoundFunctionName: return(BindRound(node)); case ClrCanonicalFunctions.FloorFunctionName: return(BindFloor(node)); case ClrCanonicalFunctions.CeilingFunctionName: return(BindCeiling(node)); default: throw new NotImplementedException(Error.Format(SRResources.ODataFunctionNotSupported, node.Name)); } }
/// <summary> /// Translates a function call which returns a single value. /// </summary> /// <param name="functionCallNode">The function call node to translate.</param> /// <returns>The translated expression which evaluates to the result of the function call.</returns> protected virtual Expression TranslateSingleValueFunctionCall(SingleValueFunctionCallQueryNode functionCallNode) { ExceptionUtils.CheckArgumentNotNull(functionCallNode, "functionCallNode"); ExceptionUtils.CheckArgumentNotNull(functionCallNode.Name, "functionCallNode.Name"); // First check that all arguments are single nodes and build a list so that we can go over them more than once. List<SingleValueQueryNode> argumentNodes = new List<SingleValueQueryNode>(); if (functionCallNode.Arguments != null) { foreach (QueryNode argumentNode in functionCallNode.Arguments) { SingleValueQueryNode singleValueArgumentNode = argumentNode as SingleValueQueryNode; if (singleValueArgumentNode == null) { throw new ODataException(Strings.QueryExpressionTranslator_FunctionArgumentNotSingleValue(functionCallNode.Name)); } argumentNodes.Add(singleValueArgumentNode); } } // Find the exact matching signature BuiltInFunctionSignature[] signatures; if (!BuiltInFunctions.TryGetBuiltInFunction(functionCallNode.Name, out signatures)) { throw new ODataException(Strings.QueryExpressionTranslator_UnknownFunction(functionCallNode.Name)); } BuiltInFunctionSignature signature = (BuiltInFunctionSignature)TypePromotionUtils.FindExactFunctionSignature( signatures, argumentNodes.Select(argumentNode => argumentNode.TypeReference).ToArray()); if (signature == null) { throw new ODataException( Strings.QueryExpressionTranslator_NoApplicableFunctionFound( functionCallNode.Name, BuiltInFunctions.BuildFunctionSignatureListDescription(functionCallNode.Name, signatures))); } // Translate all arguments into expressions Expression[] argumentExpressions = new Expression[argumentNodes.Count]; Expression[] nonNullableArgumentExpression = new Expression[argumentNodes.Count]; for (int argumentIndex = 0; argumentIndex < argumentNodes.Count; argumentIndex++) { SingleValueQueryNode argumentNode = argumentNodes[argumentIndex]; Expression argumentExpression = this.Translate(argumentNode); argumentExpressions[argumentIndex] = argumentExpression; // The below code relies on the fact that all built-in functions do not take nullable parameters (in CLR space) // so if we do see a nullable as one of the parameters, we cast it down to non-nullable // If null propagation is on, we may deal with the nullable aspect later on by injecting conditionals around the function call // which handle the null, or if null propagation is of, passing the potential null is OK since the LINQ provider is expected // to handle such cases then. if (TypeUtils.IsNullableType(argumentExpression.Type)) { nonNullableArgumentExpression[argumentIndex] = Expression.Convert(argumentExpression, TypeUtils.GetNonNullableType(argumentExpression.Type)); } else { nonNullableArgumentExpression[argumentIndex] = argumentExpression; } } // Build the function call expression (as if null propagation is not required and nulls can be handled anywhere) Expression functionCallExpression = signature.BuildExpression(nonNullableArgumentExpression); // Apply the null propagation // Note that we inject null propagation on all arguments, which means we assume that if any of the arguments passed to function // is null, the entire function is null. This is true for all the built-in functions for now. If this would be to change we would have to review // the following code. if (this.nullPropagationRequired) { for (int argumentIndex = 0; argumentIndex < argumentExpressions.Length; argumentIndex++) { Expression argumentExpression = argumentExpressions[argumentIndex]; // Don't null propagate the lambda parameter since it will never be null // or if the argument expression can't be null due to its type. if (argumentExpression == this.parameterNodeDefinitions.Peek().Value || !TypeUtils.TypeAllowsNull(argumentExpression.Type)) { continue; } // Tiny optimization: remove the check on constants which are known not to be null. // Otherwise every string literal propagates out, which is correct but unnecessarily messy. ConstantExpression constantExpression = argumentExpression as ConstantExpression; if (constantExpression != null && constantExpression.Value != null) { continue; } Expression test = Expression.Equal(argumentExpression, Expression.Constant(null, argumentExpression.Type)); Expression falseBranch = functionCallExpression; if (!TypeUtils.TypeAllowsNull(falseBranch.Type)) { falseBranch = Expression.Convert(falseBranch, typeof(Nullable<>).MakeGenericType(falseBranch.Type)); } Expression trueBranch = Expression.Constant(null, falseBranch.Type); functionCallExpression = Expression.Condition(test, trueBranch, falseBranch); } } return functionCallExpression; }
private string BindFloor(SingleValueFunctionCallQueryNode fnNode) { var args = fnNode.Arguments.Select(BindArgument).ToList(); return "floor(" + args[0] + ")"; }
private string BindConcat(SingleValueFunctionCallQueryNode fnNode) { var args = fnNode.Arguments.Select(BindArgument).ToList(); return "concat(" + String.Join(", ", args) + ")"; }
private string BindTrim(SingleValueFunctionCallQueryNode fnNode) { // SPARQL has no trim function, so we use regular expressions with replace() var args = fnNode.Arguments.Select(BindArgument).ToList(); return "replace(" + args[0] + @", '^\s+|\s+$', '')"; // replace ws at start or end of string with empty string }
private string BindSubstringOf(SingleValueFunctionCallQueryNode fnNode) { // SPARQL equivalent is contains(str, str) with argument ordering switched var args = fnNode.Arguments.Select(BindArgument).ToList(); return "contains(" + args[1] + ", " + args[0] + ")"; }
private string BindSubstring(SingleValueFunctionCallQueryNode fnNode) { var args = fnNode.Arguments.Select(BindArgument).ToList(); return "substr(" + String.Join(", ", args.Take(3)) + ")"; }
private string BindStartsWith(SingleValueFunctionCallQueryNode fnNode) { var args = fnNode.Arguments.Select(BindArgument).ToList(); return("strstarts(" + args[0] + ", " + args[1] + ")"); }
private string BindStartsWith(SingleValueFunctionCallQueryNode fnNode) { var args = fnNode.Arguments.Select(BindArgument).ToList(); return "strstarts(" + args[0] + ", " + args[1] + ")"; }
private string BindSingleValueFunctionCall(SingleValueFunctionCallQueryNode fnNode) { switch (fnNode.Name.ToLowerInvariant()) { case "substringof": return(BindSubstringOf(fnNode)); case "endswith": return(BindEndsWith(fnNode)); case "startswith": return(BindStartsWith(fnNode)); case "length": return(BindLength(fnNode)); case "indexof": throw new NotSupportedException("SPARQL does not support an equivalent to OData indexof(str)"); case "substring": return(BindSubstring(fnNode)); case "tolower": return(BindToLower(fnNode)); case "toupper": return(BindToUpper(fnNode)); case "trim": return(BindTrim(fnNode)); case "concat": return(BindConcat(fnNode)); // Date Functions case "day": return(BindDay(fnNode)); case "hour": return(BindHour(fnNode)); case "minute": return(BindMinute(fnNode)); case "month": return(BindMonth(fnNode)); case "second": return(BindSecond(fnNode)); case "year": return(BindYear(fnNode)); // Math functions case "round": return(BindRound(fnNode)); case "ceiling": return(BindCeiling(fnNode)); case "floor": return(BindFloor(fnNode)); default: throw new NotImplementedException("No support for function " + fnNode.Name); } }