private ODataConstantExpression ParseBinaryString(string value) { if (value.Length % 2 == 0) { var result = new byte[value.Length / 2]; for (var i = 0; i < result.Length; i++) { if (Util.IsHex(value[i * 2]) && Util.IsHex(value[i * 2 + 1])) { result[i] = (byte)(Util.HexToInt(value[i * 2]) * 16 + Util.HexToInt(value[i * 2 + 1])); } else { throw new Exception(String.Format( "Binary format is invalid at {0}.", _offset )); } } return(ODataExpression.Constant(result)); } throw new Exception(String.Format("Binary format is invalid at {0}.", _offset)); }
protected override void VisitConvert(ODataConvertExpression node) { var convertExpression = node.Expression.Type.IsImplicityCastableTo(node.Type) ? node.Expression : ODataExpression.Call(ODataFunction.Cast, new[] { node.Expression, ODataExpression.Constant(node.ClrType) }); this.Visit(convertExpression); }
private ODataConstantExpression ParceValue(string typeName, string rawValue) { var type = GetType(typeName); var value = TypeDescriptor.GetConverter(type).ConvertFromString(rawValue); return(ODataExpression.Constant(value)); }
private ODataConstantExpression ParseGuidString(string value) { var match = GuidRegex.Match(value); if (match.Success) { if (Guid.TryParse(match.Groups[1].Value, out var guid)) { return(ODataExpression.Constant(guid)); } } throw new FormatException($"Could not read '{value}' as Guid at {_offset}."); }
private ODataConstantExpression ParseString() { var sb = new StringBuilder(); var hadEnd = false; for (_current++; _current < _source.Length; _current++) { char c = _source[_current]; if (c == '\'') { // Two consecutive quotes translate to a single quote in // the string. This is not in the spec (2.2.2), but seems // the logical thing to do (and at StackOverflow on // http://stackoverflow.com/questions/3979367 they seem // to think the same thing). if ( _current < _source.Length - 1 && _source[_current + 1] == '\'' ) { _current++; sb.Append('\''); } else { hadEnd = true; break; } } else { sb.Append(c); } } if (!hadEnd) { throw new Exception(String.Format( "Unterminated string starting at {0}.", _offset )); } _offset = _current + 1; return(ODataExpression.Constant(sb.ToString())); }
private ODataConstantExpression ParseTimeString(string value) { var match = TimeSpanRegex.Match(value); if (match.Success) { try { var timespan = XmlConvert.ToTimeSpan(match.Groups[1].Value); return(ODataExpression.Constant(timespan)); } catch { throw new FormatException("Could not read " + value + " as TimeSpan."); } } throw new Exception(String.Format("Duration format is invalid at {0}.", _offset)); }
private ODataConstantExpression ParseGuidString(string value, bool isStrict = false) { var match = GuidRegex.IsMatch(value); if (match) { Guid guid; if (Guid.TryParse(value, out guid)) { return(ODataExpression.Constant(guid)); } } if (isStrict) { throw new FormatException(String.Format("Could not read '{0}' as Guid at {1}.", value, _offset)); } else { return(null); } }
private ODataExpression ParseSimple() { if (this.TryEat(ODataTokenKind.LeftParen)) { // parse group var group = this.ParseExpression(); this.Eat(ODataTokenKind.RightParen); return(group); } ODataToken next; if (this.TryEat(ODataTokenKind.Identifier, out next)) { if (this.TryEat(ODataTokenKind.LeftParen)) { // parse function ODataFunction function; if (!Enum.TryParse(next.Text, ignoreCase: true, result: out function)) { throw new ODataParseException(next.Text + " is not a known ODataFunction!"); } var arguments = this.ParseExpressionList(this.ParseExpression, ODataTokenKind.Comma); this.Eat(ODataTokenKind.RightParen); if (function == ODataFunction.IsOf || function == ODataFunction.Cast) { var typeLiteral = this.ReParseAsType(arguments[arguments.Count - 1]); var argumentsCopy = arguments.ToArray(); argumentsCopy[arguments.Count - 1] = ODataExpression.Constant(typeLiteral); arguments = argumentsCopy; } return(ODataExpression.Call(function, arguments)); } // parse member access var type = this._elementType; // root element type ODataMemberAccessExpression access = null; // root member while (true) { // get the property var property = typeof(ODataObject).IsAssignableFrom(type) ? ODataEntity.GetProperty(next.Text, typeof(ODataObject)) : type.GetProperty(next.Text, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase); if (property == null) { throw new ODataParseException("Property '" + next.Text + "' could not be found on type " + type.FullName); } access = ODataExpression.MemberAccess(access, property); // if we don't see '/' followed by an identifier, we're done // we can't just use TryEat() here, because we need to distinguish between Foo/Bar/* and Foo/Bar/Baz if (this.Next().Kind != ODataTokenKind.Slash || this.Next(2).Kind != ODataTokenKind.Identifier) { break; } // otherwise, update next to the next id and then advance type down the property chain this.Eat(ODataTokenKind.Slash); next = this.Eat(ODataTokenKind.Identifier); type = property.PropertyType; } return(access); } // literals if (this.TryEat(ODataTokenKind.NullLiteral, out next)) { return(ODataExpression.Constant(null)); } if (this.TryEat(ODataTokenKind.BinaryLiteral, out next)) { throw new NotImplementedException("Binary Literal Parse"); } if (this.TryEat(ODataTokenKind.BooleanLiteral, out next)) { return(ODataExpression.Constant(bool.Parse(next.Text))); } // see comment on the enum //if (this.TryEat(ODataTokenKind.ByteLiteral, out next)) //{ // return ODataExpression.Constant(Convert.ToByte(next.Text, fromBase: 16)); //} if (this.TryEat(ODataTokenKind.DateTimeLiteral, out next)) { Func <string, string> zeroIfEmpty = s => s.Length == 0 ? "0" : s; var dateTime = new DateTime( year: int.Parse(next.Match.Groups["year"].Value), month: int.Parse(next.Match.Groups["month"].Value), day: int.Parse(next.Match.Groups["day"].Value), hour: int.Parse(next.Match.Groups["hour"].Value), minute: int.Parse(next.Match.Groups["minute"].Value), second: int.Parse(zeroIfEmpty(next.Match.Groups["second"].Value)), millisecond: 0 ) .AddSeconds(double.Parse(zeroIfEmpty(next.Match.Groups["fraction"].Value))); return(ODataExpression.Constant(dateTime)); } if (this.TryEat(ODataTokenKind.DecimalLiteral, out next)) { return(ODataExpression.Constant(decimal.Parse(next.Text.Substring(0, next.Text.Length - 1)))); } if (this.TryEat(ODataTokenKind.DoubleLiteral, out next)) { return(ODataExpression.Constant(double.Parse(next.Text))); } if (this.TryEat(ODataTokenKind.SingleLiteral, out next)) { return(ODataExpression.Constant(float.Parse(next.Text.Substring(0, next.Text.Length - 1)))); } if (this.TryEat(ODataTokenKind.GuidLiteral, out next)) { return(ODataExpression.Constant(Guid.Parse(next.Match.Groups["digits"].Value))); } if (this.TryEat(ODataTokenKind.Int32Literal, out next)) { // Note: this will fail hard if we have an out-of-range int value. However, this is consistent // with MSFT's implementation (see http://services.odata.org/v3/odata/odata.svc/Products?$format=json&$filter=Price%20ge%202147483648) return(ODataExpression.Constant(int.Parse(next.Text))); } if (this.TryEat(ODataTokenKind.Int64Literal, out next)) { return(ODataExpression.Constant(long.Parse(next.Text.Substring(0, next.Text.Length - 1)))); } if (this.TryEat(ODataTokenKind.StringLiteral, out next)) { // unescaping, from http://stackoverflow.com/questions/3979367/how-to-escape-a-single-quote-to-be-used-in-an-odata-query return(ODataExpression.Constant(next.Match.Groups["chars"].Value.Replace("''", "'"))); } throw new ODataParseException("Unexpected token " + this.Next()); }
protected override void VisitQuery(ODataQueryExpression node) { const string RowNumberColumnName = "__medallionODataRowNumber"; var isCounting = node.InlineCount == ODataInlineCountOption.AllPages; // we never do pagination when doing counting. This is because, in OData, count ignores pagination var hasPagination = !isCounting && (node.Skip > 0 || node.Top.HasValue); // we special-case "top 0" and just do WHERE 1=0 instead. This is because offset-fetch // does not allow a fetch of 0 if (hasPagination && (node.Top ?? 1) == 0) { var emptyQuery = node.Update( filter: ODataExpression.Constant(false), top: null ); this.Visit(emptyQuery); return; } if (isCounting) { this.WriteLine("SELECT COUNT(1) AS theCount") .WriteLine("FROM ("); } var hasRowNumberPagination = hasPagination && this.syntaxProvider.Pagination == SqlSyntax.PaginationSyntax.RowNumber; if (hasRowNumberPagination) { this.Write("SELECT ") .WriteCommaDelimitedList(node.Select, ifEmpty: "*") .WriteLine() .WriteLine("FROM ("); } // select this.Write("SELECT "); if (hasRowNumberPagination) { this.Write("* , ROW_NUMBER() OVER (ORDER BY ") .WriteCommaDelimitedList(node.OrderBy, ifEmpty: "RAND()") .Write(") AS ").Write(RowNumberColumnName); } else { this.WriteCommaDelimitedList(node.Select, ifEmpty: "*"); } this.WriteLine(); // from this.Write("FROM ").Write(this.tableSql).Write(" ").Write(Alias).WriteLine(); // where if (node.Filter != null) { this.Write("WHERE ").Write(node.Filter, boolMode: BoolMode.Bool).WriteLine(); } if (hasRowNumberPagination) { this.Write(") ").WriteLine(Alias); // close the subquery this.Write("WHERE "); this.syntaxProvider.RenderParameterReference(s => this.Write(s), this.CreateParameter(ODataExpression.Constant(node.Skip))); this.Write(" < ").Write(Alias).Write(".").Write(RowNumberColumnName); if (node.Top.HasValue) { this.Write(" AND ").Write(Alias).Write(".").Write(RowNumberColumnName).Write(" <= "); this.syntaxProvider.RenderParameterReference(s => this.Write(s), this.CreateParameter(ODataExpression.Constant(node.Skip + node.Top.Value))); } this.WriteLine(); } // order by // we avoid rendering orderby when counting. We don't have to worry about pagination // since hasPagination is always false when counting if ((node.OrderBy.Count > 0 && !isCounting) // when doing offset-fetch pagination, we are required to have an order by clause || (hasPagination && this.syntaxProvider.Pagination == SqlSyntax.PaginationSyntax.OffsetFetch)) { this.Write("ORDER BY ") .WriteCommaDelimitedList(node.OrderBy, ifEmpty: "RAND()") .WriteLine(); } // skip/take if (hasPagination) { switch (this.syntaxProvider.Pagination) { case SqlSyntax.PaginationSyntax.OffsetFetch: this.Write("OFFSET "); this.syntaxProvider.RenderParameterReference(s => this.Write(s), this.CreateParameter(ODataExpression.Constant(node.Skip))); this.WriteLine(" ROWS"); if (node.Top.HasValue) { this.Write("FETCH NEXT "); this.syntaxProvider.RenderParameterReference(s => this.Write(s), this.CreateParameter(ODataExpression.Constant(node.Top.Value))); this.WriteLine(" ROWS ONLY"); } break; case SqlSyntax.PaginationSyntax.Limit: this.Write("LIMIT ").Write(node.Skip).Write(", ") .WriteLine(node.Top ?? "18446744073709551615".As <object>()); break; case SqlSyntax.PaginationSyntax.RowNumber: // handled above break; default: throw Throw.UnexpectedCase(this.syntaxProvider.Pagination); } } if (isCounting) { this.Write(") ").WriteLine(Alias); // close the subquery } }
protected override void VisitCall(ODataCallExpression node) { // reference for different function syntax http://users.atw.hu/sqlnut/sqlnut2-chp-4-sect-4.html switch (node.Function) { case ODataFunction.Cast: this.Write("CAST(").Write(node.Arguments[0]); var toType = ((Type)((ODataConstantExpression)node.Arguments[1]).Value).ToODataExpressionType(); this.Write(" AS ").Write(this.syntaxProvider.GetSqlTypeName(toType)).Write(")"); break; case ODataFunction.Ceiling: this.VisitCallHelper(this.syntaxProvider.UseAbbreviatedCeilingFunction ? "CEIL" : "CEILING", node); break; case ODataFunction.Concat: this.Write("(").Write(node.Arguments[0]) .Write(" + ") .Write(node.Arguments[1]).Write(")"); break; case ODataFunction.Second: case ODataFunction.Minute: case ODataFunction.Hour: case ODataFunction.Day: case ODataFunction.Month: case ODataFunction.Year: this.syntaxProvider.RenderDatePartFunctionCall(node.Function, s => this.Write(s), () => this.Write(node.Arguments[0])); break; case ODataFunction.EndsWith: // endswith => (INDEXOF(needle, haystack) = LEN(haystack) - LEN(needle)) OR LEN(needle) = 0 var needleLengthExpression = ODataExpression.Call(ODataFunction.Length, new[] { node.Arguments[1] }); var endsWithExpression = ODataExpression.BinaryOp( ODataExpression.BinaryOp( ODataExpression.Call(ODataFunction.IndexOf, node.Arguments), ODataBinaryOp.Equal, ODataExpression.BinaryOp( ODataExpression.Call(ODataFunction.Length, new[] { node.Arguments[0] }), ODataBinaryOp.Subtract, needleLengthExpression ) ), ODataBinaryOp.Or, ODataExpression.BinaryOp( needleLengthExpression, ODataBinaryOp.Equal, ODataExpression.Constant(0) ) ); this.Visit(endsWithExpression); break; case ODataFunction.StartsWith: // startswith => INDEXOF(needle, haystack) = 0 var startsWithExpression = ODataExpression.BinaryOp( ODataExpression.Call(ODataFunction.IndexOf, node.Arguments), ODataBinaryOp.Equal, ODataExpression.Constant(0) ); this.Visit(startsWithExpression); break; case ODataFunction.SubstringOf: // substringof => INDEXOF(needle, haystack) >= 0 var substringOfExpression = ODataExpression.BinaryOp( ODataExpression.Call(ODataFunction.IndexOf, node.Arguments.Reverse()), ODataBinaryOp.GreaterThanOrEqual, ODataExpression.Constant(0) ); this.Visit(substringOfExpression); break; case ODataFunction.IndexOf: this.syntaxProvider.RenderIndexOfFunctionCall(s => this.Write(s), renderNeedleArgument: () => this.Write(node.Arguments[1]), renderHaystackArgument: () => this.Write(node.Arguments[0])); break; case ODataFunction.Floor: this.VisitCallHelper("FLOOR", node); break; case ODataFunction.Length: this.VisitCallHelper(this.syntaxProvider.StringLengthFunctionName, node); break; case ODataFunction.Replace: this.VisitCallHelper("REPLACE", node); break; case ODataFunction.Round: this.syntaxProvider.RenderRoundFunctionCall(s => this.Write(s), () => this.Write(node.Arguments[0])); break; case ODataFunction.Substring: this.syntaxProvider.RenderSubstringFunctionCall(s => this.Write(s), () => this.Write(node.Arguments[0]), () => this.Write(node.Arguments[1]), node.Arguments.Count > 2 ? new Action(() => this.Write(node.Arguments[2])) : null); break; case ODataFunction.ToLower: this.VisitCallHelper("LOWER", node); break; case ODataFunction.ToUpper: this.VisitCallHelper("UPPER", node); break; case ODataFunction.Trim: if (this.syntaxProvider.HasTwoSidedTrim) { this.VisitCallHelper("TRIM", node); } else { // call both LTRIM and RTRIM this.VisitCallHelper("LTRIM(RTRIM", node); this.Write(")"); } break; case ODataFunction.IsOf: throw new NotSupportedException(node.Function.ToString()); } }
private ODataExpression TranslateInternal(Expression linq) { if (linq == null) { return(null); } // captured parameters object value; if (TryGetValueFast(linq, out value)) { // special case the handling of queryable constants, since these can be the "root" // of the query expression tree. We check for isInsideQuery since we don't allow multiple // roots. An example of a multiply-rooted tree would be: q.Where(x => q.Any(xx => xx.A < x.A)) if (!this._isInsideQuery && typeof(IQueryable).GetTypeInfo().IsAssignableFrom(linq.Type.GetTypeInfo())) { this._rootQuery = (IQueryable)value; return(ODataExpression.Query()); } return(ODataExpression.Constant(value, linq.Type)); } switch (linq.NodeType) { case ExpressionType.OrElse: return(this.TranslateBinary(linq, ODataBinaryOp.Or)); case ExpressionType.AndAlso: return(this.TranslateBinary(linq, ODataBinaryOp.And)); case ExpressionType.Add: if (linq.Type == typeof(string)) { // special case adding strings, since that's the same as Concat var binary = (BinaryExpression)linq; return(this.TranslateInternal(Expression.Call(Helpers.GetMethod(() => string.Concat(default(string), default(string))), binary.Left, binary.Right))); } return(this.TranslateBinary(linq, ODataBinaryOp.Add)); case ExpressionType.Subtract: return(this.TranslateBinary(linq, ODataBinaryOp.Subtract)); case ExpressionType.Multiply: return(this.TranslateBinary(linq, ODataBinaryOp.Multiply)); case ExpressionType.Divide: return(this.TranslateBinary(linq, ODataBinaryOp.Divide)); case ExpressionType.Modulo: return(this.TranslateBinary(linq, ODataBinaryOp.Modulo)); case ExpressionType.Equal: return(this.TranslateBinary(linq, ODataBinaryOp.Equal)); case ExpressionType.NotEqual: return(this.TranslateBinary(linq, ODataBinaryOp.NotEqual)); case ExpressionType.LessThan: return(this.TranslateBinary(linq, ODataBinaryOp.LessThan)); case ExpressionType.LessThanOrEqual: return(this.TranslateBinary(linq, ODataBinaryOp.LessThanOrEqual)); case ExpressionType.GreaterThan: return(this.TranslateBinary(linq, ODataBinaryOp.GreaterThan)); case ExpressionType.GreaterThanOrEqual: return(this.TranslateBinary(linq, ODataBinaryOp.GreaterThanOrEqual)); case ExpressionType.Not: return(ODataExpression.UnaryOp(this.TranslateInternal(((UnaryExpression)linq).Operand), ODataUnaryOp.Not)); case ExpressionType.Negate: // we translate -a.B as (-1 * a.B) for numeric types var negated = ((UnaryExpression)linq).Operand; var underlyingNegatedType = Nullable.GetUnderlyingType(negated.Type) ?? negated.Type; if (!underlyingNegatedType.IsNumeric()) { throw new ODataCompileException("Expression '" + linq + "' could not be translated to OData: only negation of numeric types is supported"); } var multiplyNegate = Expression.Multiply( Expression.Constant(Convert.ChangeType(-1, underlyingNegatedType), negated.Type), negated ); return(this.TranslateInternal(multiplyNegate)); case ExpressionType.MemberAccess: return(this._memberAndParameterTranslator.TranslateMemberAccess((MemberExpression)linq)); case ExpressionType.Convert: case ExpressionType.ConvertChecked: case ExpressionType.TypeAs: return(ODataExpression.Convert(this.TranslateInternal(((UnaryExpression)linq).Operand), linq.Type)); case ExpressionType.Parameter: return(this._memberAndParameterTranslator.TranslateParameter((ParameterExpression)linq)); case ExpressionType.TypeIs: var typeBinary = (TypeBinaryExpression)linq; var isArg = this.TranslateInternal(typeBinary.Expression); var isTypeConstant = ODataExpression.Constant(typeBinary.TypeOperand); return(isArg != null ? ODataExpression.Call(ODataFunction.IsOf, new[] { isArg, isTypeConstant }) : ODataExpression.Call(ODataFunction.IsOf, new[] { isTypeConstant })); case ExpressionType.Call: return(this.TranslateCall((MethodCallExpression)linq)); case ExpressionType.Quote: var quoted = (LambdaExpression)((UnaryExpression)linq).Operand; if (!this._isInsideQuery) { throw new ODataCompileException("Unexpected placement for lambda expression " + linq); } return(this.TranslateInternal(quoted.Body)); default: throw new ODataCompileException("Expression '" + linq + "' of type " + linq.NodeType + " could not be translated to OData"); } }
private ODataExpression TranslateCall(MethodCallExpression call) { // query operators if (call.Method.DeclaringType == typeof(Queryable)) { if (this._isInsideQuery) { throw new ODataCompileException("OData does not support nested query structures!"); } // normalize overloads of query operators bool changedAllToAny; var normalized = QueryOperatorCanonicalizer.Canonicalize(call, changedAllToAny: out changedAllToAny); if (normalized != call) { // TODO FUTURE better exception message here since expression being translated won't match? var result = this.TranslateInternal(normalized); if (changedAllToAny) { var innerResultTranslator = this._resultTranslator; Throw <InvalidOperationException> .If(innerResultTranslator == null, "Sanity check: expected non-null result translator"); this._resultTranslator = (values, count) => !((bool)innerResultTranslator(values, count)); } return(result); } // handle "normal" query methods // translate the source query. If the source is a constant, that's the "root" of the query tree // e. g. that might be the OData equivalent of a DbSet or ObjectQuery constant var source = (ODataQueryExpression)this.TranslateInternal(call.Arguments[0]); switch (call.Method.Name) { case "Where": // not the index version if (call.Arguments[1].Type.IsGenericOfType(typeof(Func <, ,>))) { goto default; } var predicate = this.TranslateInsideQuery(call.Arguments[1]); if (source.OrderBy.Count > 0 || source.Top.HasValue || source.Skip > 0) { throw new ODataCompileException("Cannot apply a filter after applying an OrderBy, Take, or Skip operation"); } return(source.Update(filter: source.Filter != null ? ODataExpression.BinaryOp(source.Filter, ODataBinaryOp.And, predicate) : predicate)); case "OrderBy": case "OrderByDescending": case "ThenBy": case "ThenByDescending": if (call.Arguments.Count != 2) { goto default; } var sortKeyExpression = this.TranslateInsideQuery(call.Arguments[1]); var sortKey = ODataExpression.SortKey(sortKeyExpression, descending: call.Method.Name.EndsWith("Descending")); if (source.Top.HasValue || source.Skip > 0) { throw new ODataCompileException("Cannot apply a sort after applying Take or Skip operations"); } return(source.Update( orderBy: call.Method.Name.StartsWith("Then") ? source.OrderBy.Concat(sortKey.Enumerate()) : sortKey.Enumerate() )); case "Skip": object skip; Throw <InvalidOperationException> .If(!TryGetValueFast(call.Arguments[1], out skip), "Could not get value"); if (source.Top.HasValue) { throw new ODataCompileException("Cannot apply a skip after applying a Take operation"); } return(source.Update(skip: source.Skip + (int)skip)); // not right case "Take": object take; Throw <InvalidOperationException> .If(!TryGetValueFast(call.Arguments[1], out take), "Could not get value"); return(Take(source, (int)take)); case "Select": /* * MA: select is tricky in OData, since it doesn't just conform to the OData $select system query option. * Instead, you could have an intermediate select in your query, and it would be nice to be able to support that. * In that case, OData still forces us to select original columns, but we can store away the projection expressions * to re-apply later. We also need to make sure to store how to map each projected property, so that we can inline these * projections. * * For example, imagine you have the query q.Select(x => new { a = x.B + 2 }).Where(t => t.a > 5). * In OData, this would translate to "$filter=B + 2 > 5&$select=B". We can then do the final projection to the anonymous * type in memory on the client side */ // we don't support select with index if (!call.Arguments[1].Type.GetGenericArguments(typeof(Expression <>)).Single().IsGenericOfType(typeof(Func <,>))) { goto default; } // unquote and extract the lambda var projection = ((LambdaExpression)((UnaryExpression)call.Arguments[1]).Operand); // register the projection this._isInsideQuery = true; this._memberAndParameterTranslator.RegisterProjection(projection); this._isInsideQuery = false; // return the source, since the projection doesn't actually affect the returned expression // until the very end when we can use it to determine which columns to $select return(source); case "Any": this._resultTranslator = MakeTranslator(call.Arguments[0], e => e.Any()); return(Take(source, 1)); case "Count": this._resultTranslator = (values, count) => ComputeCount(count: count.Value, skip: source.Skip, top: source.Top); // top 0 is so that we don't have any wasted payload of data that we won't look at return(source.Update(inlineCount: ODataInlineCountOption.AllPages, top: 0)); case "LongCount": this._resultTranslator = (values, count) => (long)ComputeCount(count: count.Value, skip: source.Skip, top: source.Top); // top 0 is so that we don't have any wasted payload of data that we won't look at return(source.Update(inlineCount: ODataInlineCountOption.AllPages, top: 0)); case "First": this._resultTranslator = MakeTranslator(call.Arguments[0], e => e.First()); return(Take(source, 1)); case "FirstOrDefault": this._resultTranslator = MakeTranslator(call.Arguments[0], e => e.FirstOrDefault()); return(Take(source, 1)); case "Single": this._resultTranslator = MakeTranslator(call.Arguments[0], e => e.Single());; return(Take(source, 2)); case "SingleOrDefault": this._resultTranslator = MakeTranslator(call.Arguments[0], e => e.SingleOrDefault());; return(Take(source, 2)); default: throw new ODataCompileException("Query operator " + call.Method + " is not supported in OData"); } } // other OData methods // Enumerable/Collection contains (e. g. "IN"). This gets handled before the odata functions because // the thisExpression can't be translated normally. Since Contains() is declared on a bunch of different collection // types, we basically check that (1) there are either 2 arguments (static method) or an instance + 1 argument // (2) that the container argument/instance is an in-memory "constant", (3) that the collection object has an IEnumerable<T> // element type, and (4) that the test argument is of that element type // Finally, we translate the IN clause to a set of ORs object enumerable; Type elementType; if (call.Method.Name == "Contains" && call.Arguments.Count + Convert.ToInt32(!call.Method.IsStatic) == 2 && TryGetValueFast(call.Object ?? call.Arguments[0], out enumerable) && enumerable != null && (elementType = enumerable.GetType().GetGenericArguments(typeof(IEnumerable <>)).SingleOrDefault()) != null && call.Arguments.Last().Type == elementType) { var testExpression = this.TranslateInternal(call.Arguments.Last()); var equalsElementExpressions = ((IEnumerable)enumerable).Cast <object>() .Select(o => ODataExpression.Constant(o, elementType)) .Select(c => ODataExpression.BinaryOp(testExpression, ODataBinaryOp.Equal, c)) .ToArray(); var equivalentOrExpression = equalsElementExpressions.Length == 0 ? ODataExpression.Constant(true).As <ODataExpression>() : equalsElementExpressions.Aggregate((e1, e2) => ODataExpression.BinaryOp(e1, ODataBinaryOp.Or, e2)); return(equivalentOrExpression); } // ODataFunctions var thisExpression = this.TranslateInternal(call.Object); var translatedArgs = call.Arguments.Select(this.TranslateInternal); // string functions if (call.Method.DeclaringType == typeof(string) && call.Method.Name == "Substring") { return(ODataExpression.Call(ODataFunction.Substring, thisExpression.Enumerate().Concat(translatedArgs))); } if (call.Method.DeclaringType == typeof(string) && call.Method.Name == "Replace" && call.Arguments[0].Type == typeof(string)) { // for now, we don't support the char replace overload return(ODataExpression.Call(ODataFunction.Replace, thisExpression.Enumerate().Concat(translatedArgs))); } if (call.Method.DeclaringType == typeof(string) && call.Method.Name == "Concat" && call.Arguments.All(a => a.Type == typeof(string))) { // we support only string concats, but with any fixed number of parameters return(translatedArgs.Aggregate((s1, s2) => ODataExpression.Call(ODataFunction.Concat, new[] { s1, s2 }))); } if (call.Method.DeclaringType == typeof(string) && call.Method.Name == "StartsWith" && call.Arguments.Count == 1) { return(ODataExpression.Call(ODataFunction.StartsWith, new[] { thisExpression, translatedArgs.Single() })); } if (call.Method.DeclaringType == typeof(string) && call.Method.Name == "EndsWith" && call.Arguments.Count == 1) { return(ODataExpression.Call(ODataFunction.EndsWith, new[] { thisExpression, translatedArgs.Single() })); } if (call.Method.DeclaringType == typeof(string) && call.Method.Name == "IndexOf" && call.Arguments.Count == 1 && call.Arguments[0].Type == typeof(string)) { return(ODataExpression.Call(ODataFunction.IndexOf, new[] { thisExpression, translatedArgs.Single() })); } if (call.Method.DeclaringType == typeof(string) && call.Method.Name == "Contains" && call.Arguments.Count == 1 && call.Arguments[0].Type == typeof(string)) { // note: we reverse the args here because A.SubstringOf(B) is equivalent to B.Contains(A) return(ODataExpression.Call(ODataFunction.SubstringOf, new[] { translatedArgs.Single(), thisExpression })); } if (call.Method.DeclaringType == typeof(string) && call.Method.Name == "ToLower" && call.Arguments.Count == 0) { return(ODataExpression.Call(ODataFunction.ToLower, thisExpression.Enumerate())); } if (call.Method.DeclaringType == typeof(string) && call.Method.Name == "ToUpper" && call.Arguments.Count == 0) { return(ODataExpression.Call(ODataFunction.ToUpper, thisExpression.Enumerate())); } if (call.Method.DeclaringType == typeof(string) && call.Method.Name == "Trim" && call.Arguments.Count == 0) { return(ODataExpression.Call(ODataFunction.Trim, thisExpression.Enumerate())); } // math functions if (call.Method.DeclaringType == typeof(Math) && call.Method.Name == "Ceiling") { return(ODataExpression.Call(ODataFunction.Ceiling, translatedArgs)); } if (call.Method.DeclaringType == typeof(Math) && call.Method.Name == "Floor") { return(ODataExpression.Call(ODataFunction.Floor, translatedArgs)); } if (call.Method.DeclaringType == typeof(Math) && call.Method.Name == "Round" && call.Arguments.Count == 1) { return(ODataExpression.Call(ODataFunction.Round, translatedArgs)); } throw new ODataCompileException("Method " + call.Method + " could not be translated to OData"); }
private ODataExpression ParseIdentifier(bool minus, ODataParameterExpression parameter, ODataExpression parent = null, IDictionary <string, ODataParameterExpression> lambdaParameters = null) { for (_current++; _current < _source.Length; _current++) { var c = _source[_current]; if (!IsIdentifierChar(c)) { break; } } var name = _source.Substring(_offset, _current - _offset); var lastOffset = _offset; _offset = _current; switch (name) { case "INF": return(ODataExpression.Constant(double.PositiveInfinity)); case "-INF": return(ODataExpression.Constant(double.NegativeInfinity)); case "Nan": return(ODataExpression.Constant(double.NaN)); case "true": return(ODataExpression.Constant(true)); case "false": return(ODataExpression.Constant(false)); case "null": return(ODataExpression.Constant(null, typeof(object))); case "-": { return(new ODataUnaryExpression(ExpressionType.Negate)); } default: if (minus) { // Reset the offset. _offset = lastOffset + 1; return(new ODataUnaryExpression(ExpressionType.Negate)); } break; } if (_offset < _source.Length) { switch (_source[_offset]) { case '\'': { StringType stringType; switch (name) { case "X": stringType = StringType.Binary; break; case "binary": stringType = StringType.Binary; break; case "datetime": stringType = StringType.DateTime; break; case "guid": stringType = StringType.Guid; break; case "time": stringType = StringType.Time; break; case "datetimeoffset": stringType = StringType.DateTimeOffset; break; default: stringType = StringType.None; break; } if (stringType != StringType.None && _source[_offset] == '\'') { var content = ParseString(); return(ParseSpecialString((string)content.Value, stringType)); } if (stringType == StringType.None) { var content = ParseString(); return(ParceValue(name, (string)content.Value)); } break; } case ':': { _offset++; var depth = 0; var p = ODataExpression.Parameter(name); var lp = new Dictionary <string, ODataParameterExpression>(lambdaParameters ?? new Dictionary <string, ODataParameterExpression>()); lp[p.Name] = p; var tokens = new List <ODataExpression>(); while (true) { var token = GetNext(parameter, null, lp); if (token == null) { break; } if (token.NodeType == ExpressionType.Default) { var syntaxExpressionToken = (ODataSyntaxExpression)token; if (syntaxExpressionToken.Syntax == ',') { _offset--; break; } if (syntaxExpressionToken.Syntax == '(') { depth++; } if (syntaxExpressionToken.Syntax == ')') { if (depth == 0) { _offset--; break; } depth--; } } tokens.Add(token); } var body = CreateExpression(ConvertToRpn(tokens)); var lambdaExpression = new ODataLambdaExpression { Parameters = new[] { p }, Body = body }; return(lambdaExpression); } case '/': { _offset++; if (lambdaParameters != null && lambdaParameters.ContainsKey(name) && parent == null) { return(ParseIdentifier(false, parameter, lambdaParameters[name], lambdaParameters)); } if (name.StartsWith("Ase.") && parent != null && name.Substring(4) == parent.ToString().Replace(parameter.ToString() + ".", "")) { return(ParseIdentifier(false, parameter, parent, lambdaParameters)); } return(ParseIdentifier(false, parameter, ODataExpression.PropertyOrField(name, parent ?? parameter), lambdaParameters)); } case '(': //Если следующий элемент скобка, значит это функция { var depth = 0; var comma = false; var arguments = new List <ODataExpression>(); var temp = new List <ODataExpression>(); while (true) { var token = GetNext(parameter, null, lambdaParameters); if (token == null) { break; } var syntax = token as ODataSyntaxExpression; if (syntax != null && syntax.Syntax == ',') { if (temp.Any()) { var tokens = ConvertToRpn(temp.ToArray()); var expression = CreateExpression(tokens); arguments.Add(expression); temp = new List <ODataExpression>(); comma = true; } else { throw new Exception("extra comma"); } } else { if (syntax != null && syntax.Syntax == '(') { if (comma) { throw new Exception("extra comma"); } depth++; } if (syntax != null && syntax.Syntax == ')') { if (comma) { throw new Exception("extra comma"); } depth--; } if (syntax == null || !(syntax.Syntax == '(' && depth == 1) && !(syntax.Syntax == ')' && depth == 0)) { temp.Add(token); } comma = false; if (depth == 0) { if (temp.Any()) { var tokens = ConvertToRpn(temp.ToArray()); var expression = CreateExpression(tokens); arguments.Add(expression); } break; } } } if (depth != 0) { throw new Exception("Parenthesis mismatch"); } var methodCallExpression = new ODataMethodCallExpression { Context = parent, MethodName = name, Arguments = arguments.ToArray() }; if (_offset < _source.Length && _source[_offset] == '/') { _current++; _offset = _offset + 1; return(ParseIdentifier(false, parameter, methodCallExpression, lambdaParameters)); } return(methodCallExpression); } } } if (name.IsOperator()) { var expressionType = name.GetExpressionType(); if (name.IsUnaryOperator()) { return(ODataExpression.MakeUnary(expressionType, null)); } if (name.IsArithmeticOperator() || name.IsLogicalOperator()) { return(ODataExpression.MakeBinary(expressionType, null, null)); } } if (parent == null && lambdaParameters != null && lambdaParameters.ContainsKey(name)) { return(lambdaParameters[name]); } if (name.Contains(".")) { var type = GetType(name); return(ODataExpression.Constant(type)); } return(ODataExpression.PropertyOrField(name, parent ?? parameter)); }
private ODataConstantExpression ParseNumeric() { var floating = false; char c; var canBeADateTime = false; for (_current++; _current < _source.Length; _current++) { c = _source[_current]; if (c == '.' && !canBeADateTime) { if (floating) { break; } floating = true; } else { if (!Char.IsDigit(c) && c != '-' && c != 'T' && c != ':' && c != '.') { canBeADateTime = true; break; } } } var haveExponent = false; if (_current < _source.Length) { c = _source[_current]; if (c == 'E' || c == 'e') { _current++; if (_source[_current] == '-') { _current++; } var exponentEnd = _current == _source.Length ? null : SkipDigits(_current); if (!exponentEnd.HasValue) { throw new Exception(String.Format( "Expected digits after exponent at {0}.", _offset )); } _current = exponentEnd.Value; haveExponent = true; if (_current < _source.Length) { c = _source[_current]; if (c == 'm' || c == 'M') { throw new Exception(String.Format( "Unexpected exponent for decimal literal at {0}.", _offset )); } if (c == 'l' || c == 'L') { throw new Exception(String.Format( "Unexpected exponent for long literal at {0}.", _offset )); } } } } var text = _source.Substring(_offset, _current - _offset); object value; if (_current < _source.Length) { c = _source[_current]; switch (c) { case 'F': case 'f': value = float.Parse(text, ParseCulture); _current++; break; case 'D': case 'd': value = double.Parse(text, ParseCulture); _current++; break; case 'M': case 'm': value = decimal.Parse(text, ParseCulture); _current++; break; case 'L': case 'l': value = long.Parse(text, ParseCulture); _current++; break; case 'Z': var dateTime = ParseSpecialString(text + c, StringType.DateTime); _current++; _offset = _current; return(dateTime); default: if (floating || haveExponent) { value = double.Parse(text, ParseCulture); } else { value = int.Parse(text, ParseCulture); } break; } } else { if (floating || haveExponent) { value = double.Parse(text, ParseCulture); } else { value = int.Parse(text, ParseCulture); } } _offset = _current; return(ODataExpression.Constant(value)); }
private ODataConstantExpression ParseDateTimeOffsetString(string value) { var dateTimeOffset = XmlConvert.ToDateTimeOffset(value); return(ODataExpression.Constant(dateTimeOffset)); }
private ODataConstantExpression ParseDateTimeString(string value) { var dateTime = DateTimeHelper.Parse(value); return(ODataExpression.Constant(dateTime)); }