diff --git a/MR3/Extensions/OData/src/Castle.MonoRail.Extension.ODataFs/AstLinqTranslator.fs b/MR3/Extensions/OData/src/Castle.MonoRail.Extension.ODataFs/AstLinqTranslator.fs index be337f79..663f17cf 100644 --- a/MR3/Extensions/OData/src/Castle.MonoRail.Extension.ODataFs/AstLinqTranslator.fs +++ b/MR3/Extensions/OData/src/Castle.MonoRail.Extension.ODataFs/AstLinqTranslator.fs @@ -24,21 +24,11 @@ module AstLinqTranslator = System.Diagnostics.Debug.Assert(m <> null, "Could not get typed_queryable_filter methodinfo") m - let typed_enumerable_filter_methodinfo = - let m = This.Assembly.GetType("Castle.MonoRail.Extension.OData.AstLinqTranslator").GetMethod("typed_enumerable_filter") - System.Diagnostics.Debug.Assert(m <> null, "Could not get typed_enumerable_filter methodinfo") - m - let typed_queryable_orderby_methodinfo = let m = This.Assembly.GetType("Castle.MonoRail.Extension.OData.AstLinqTranslator").GetMethod("typed_queryable_orderby") System.Diagnostics.Debug.Assert(m <> null, "Could not get typed_queryable_orderby methodinfo") m - let typed_enumerable_orderby_methodinfo = - let m = This.Assembly.GetType("Castle.MonoRail.Extension.OData.AstLinqTranslator").GetMethod("typed_enumerable_orderby") - System.Diagnostics.Debug.Assert(m <> null, "Could not get typed_enumerable_orderby methodinfo") - m - let select_by_key (rt:ResourceType) (source:IQueryable) (key:string) = // for now support for a single key let keyProp = Seq.head rt.KeyProperties @@ -58,14 +48,11 @@ module AstLinqTranslator = let ``method`` = typed_queryable_filter_methodinfo.MakeGenericMethod([|rtType|]) ``method``.Invoke(null, [|items; ast|]) - - let apply_queryable_orderby (rt:ResourceType) (items:IQueryable) (ast:OrderByAst seq) = let rtType = rt.InstanceType let ``method`` = typed_queryable_orderby_methodinfo.MakeGenericMethod([|rtType|]) ``method``.Invoke(null, [|items; ast|]) - let typed_select<'a> (source:IQueryable) (key:obj) (keyProp:ResourceProperty) = let typedSource = source :?> IQueryable<'a> let parameter = Expression.Parameter(source.ElementType, "element") @@ -120,19 +107,25 @@ module AstLinqTranslator = | _ -> failwithf "Unsupported binary op %O" op | _ -> failwithf "Unsupported node %O" node - - (build_tree ast, parameter) + + let exp = build_tree ast + (exp, parameter) // a predicate is a Func let build_linq_exp_predicate<'a> (paramType:Type) (ast:QueryAst) = let rootExp, parameter = build_linq_exp_tree paramType ast Expression.Lambda(rootExp, [parameter]) :?> Expression> + let build_linq_exp_lambda (paramType:Type) (ast:QueryAst) = + let rootExp, parameter = build_linq_exp_tree paramType ast + Expression.Lambda(rootExp, [parameter]) + + (* // a member access is a Func let build_linq_exp_memberaccess<'a> (paramType:Type) (ast:QueryAst) = let rootExp, parameter = build_linq_exp_tree paramType ast Expression.Lambda(rootExp, [parameter]) :?> Expression> - + *) let typed_queryable_filter<'a> (source:IQueryable) (ast:QueryAst) : IQueryable = let typedSource = source :?> IQueryable<'a> @@ -143,37 +136,35 @@ module AstLinqTranslator = let typed_queryable_orderby<'a> (source:IQueryable) (nodes:OrderByAst seq) : IQueryable = - let typedSource = source :?> IQueryable<'a> + // let typedSource = source :?> IQueryable<'a> let elemType = typeof<'a> - - let nodes = nodes |> Array.ofSeq - - let first = - if not <| Array.isEmpty nodes - then Array.get nodes 0 - else OrderByAst.Nothing - let rest = - if nodes.Length > 1 then nodes.[1..nodes.Length] - else [||] - - let createOrdered node = - match node with - | OrderByAst.Asc c -> typedSource.OrderBy ( build_linq_exp_memberaccess elemType c ) - | OrderByAst.Desc c -> typedSource.OrderByDescending ( build_linq_exp_memberaccess elemType c ) - | _ -> failwith "Unsupported node" - - let apply_then_by node elements = - elements - - let ordered = createOrdered first - // let last = rest |> Array.fold (fun last c -> apply_then_by c ) ordered - - - // let nodes |> List.ofSeq |> List.tail - // List.Cons - // nodes |> Seq.fold () - - // let exp = build_linq_exp_tree source.ElementType ast - // typedSource.OrderBy(exp).ThenBy :> IQueryable - ordered :> IQueryable + let isFirstCall = ref true + + let applyOrder (source:IQueryable) node = + let build_lambda ast : Expression * Type = + let exp = build_linq_exp_lambda elemType ast + let retType = exp.Body.Type + upcast Expression.Quote exp, retType + let asc, desc = + if !isFirstCall + then "OrderBy", "OrderByDescending" + else "ThenBy", "ThenByDescending" + isFirstCall := false + + let exp, retType, op = + match node with + | OrderByAst.Asc ast -> + let exp, retType = build_lambda ast + exp, retType, asc + | OrderByAst.Desc ast -> + let exp, retType = build_lambda ast + exp, retType, desc + | _ -> failwith "Unsupported node" + + source.Provider.CreateQuery( Expression.Call(typeof, op, [|source.ElementType; retType|], [|source.Expression; exp|]) ) + + // applies expression, which returns a "new" + // queryable, which is then used on the next call + nodes |> Seq.fold (fun source c -> applyOrder source c ) source + diff --git a/MR3/Extensions/OData/src/Castle.MonoRail.Extension.ODataFs/QueryExpressionParser.fs b/MR3/Extensions/OData/src/Castle.MonoRail.Extension.ODataFs/QueryExpressionParser.fs index 512c5a5f..fae320c5 100644 --- a/MR3/Extensions/OData/src/Castle.MonoRail.Extension.ODataFs/QueryExpressionParser.fs +++ b/MR3/Extensions/OData/src/Castle.MonoRail.Extension.ODataFs/QueryExpressionParser.fs @@ -314,7 +314,7 @@ module QueryExpressionParser = ( pstringCI "asc" |>> (fun _ -> "asc") <|> pstringCI "desc" |>> (fun _ -> "desc") <|>% "asc") |>> fun (m,o) -> if o = "asc" then OrderByExp.Asc(m) else OrderByExp.Desc(m) - let orderByUnit = sepBy1 orderbyTerm (pc ',') + let orderByUnit = sepBy1 orderbyTerm (pc ',' .>> ws) // ShipCountry , Else [asc|desc] // ShipCountry ne 'France' desc diff --git a/MR3/Extensions/OData/src/Castle.MonoRail.Extension.ODataFs/QuerySemanticAnalysis.fs b/MR3/Extensions/OData/src/Castle.MonoRail.Extension.ODataFs/QuerySemanticAnalysis.fs index 53ddab9c..ddd830af 100644 --- a/MR3/Extensions/OData/src/Castle.MonoRail.Extension.ODataFs/QuerySemanticAnalysis.fs +++ b/MR3/Extensions/OData/src/Castle.MonoRail.Extension.ODataFs/QuerySemanticAnalysis.fs @@ -262,15 +262,20 @@ module QuerySemanticAnalysis = | _ -> failwithf "Unsupported exp type %O" e - let analyze_and_convert_orderby (exps:OrderByExp[]) (rt:ResourceType) : OrderByAst seq = - - [ OrderByAst.Asc(QueryAst.Element) ] |> Seq.ofList - - let analyze_and_convert (exp:Exp) (rt:ResourceType) : QueryAst = let newTree, _ = r_analyze exp rt newTree + let analyze_and_convert_orderby (exps:OrderByExp[]) (rt:ResourceType) : OrderByAst seq = + + let convert exp = + match exp with + | OrderByExp.Asc e -> let ast, _ = r_analyze e rt in OrderByAst.Asc(ast) + | OrderByExp.Desc e -> let ast, _ = r_analyze e rt in OrderByAst.Desc(ast) + | _ -> failwithf "Unsupported OrderByExp type %O" exp + + exps |> Seq.map convert + end diff --git a/MR3/Extensions/OData/tests/Castle.MonoRail.Extension.OData.Tests/AstLinqTranslatorTestCase.cs b/MR3/Extensions/OData/tests/Castle.MonoRail.Extension.OData.Tests/AstLinqTranslatorTestCase.cs index 5da1df18..c2d44042 100644 --- a/MR3/Extensions/OData/tests/Castle.MonoRail.Extension.OData.Tests/AstLinqTranslatorTestCase.cs +++ b/MR3/Extensions/OData/tests/Castle.MonoRail.Extension.OData.Tests/AstLinqTranslatorTestCase.cs @@ -46,15 +46,63 @@ public void Init() return AstLinqTranslator.build_linq_exp_predicate(typeof(T), tree); } - private Expression> BuildOrderByExpression(string expression, ResourceType rt) + private static IQueryable ApplyOrderByExpression(IQueryable source, string expression, ResourceType rt) { var exp = QueryExpressionParser.parse_orderby(expression); // Console.WriteLine(exp4.ToStringTree()); var tree = QuerySemanticAnalysis.analyze_and_convert_orderby(exp, rt); // Console.WriteLine(tree.ToStringTree()); + return AstLinqTranslator.typed_queryable_orderby(source, tree) as IQueryable; + } + + [Test] + public void OrderBy_Asc_StringProperty() + { + var result = ApplyOrderByExpression(_catalogs.AsQueryable(), "Name", _catalogRt); + result.Should().NotBeNull(); + var names = result.Select(c => c.Name); + var expected = _catalogs.OrderBy(c => c.Name).Select(c => c.Name); + names.Should().Equal(expected); + } - // return AstLinqTranslator.build_linq_exp_memberaccess(typeof(T), tree); - return null; + [Test] + public void OrderBy_Desc_StringProperty() + { + var result = ApplyOrderByExpression(_catalogs.AsQueryable(), "Name desc", _catalogRt); + result.Should().NotBeNull(); + var names = result.Select(c => c.Name); + var expected = _catalogs.OrderByDescending(c => c.Name).Select(c => c.Name); + names.Should().Equal(expected); + } + + [Test] + public void OrderBy_Asc_Int32Property() + { + var result = ApplyOrderByExpression(_catalogs.AsQueryable(), "Id", _catalogRt); + result.Should().NotBeNull(); + var resultElems = result.Select(c => c.Id); + var expected = _catalogs.OrderBy(c => c.Id).Select(c => c.Id); + resultElems.Should().Equal(expected); + } + + [Test] + public void OrderBy_Desc_Int32Property() + { + var result = ApplyOrderByExpression(_catalogs.AsQueryable(), "Id desc", _catalogRt); + result.Should().NotBeNull(); + var resultElems = result.Select(c => c.Id); + var expected = _catalogs.OrderByDescending(c => c.Id).Select(c => c.Id); + resultElems.Should().Equal(expected); + } + + [Test] + public void OrderBy_Asc_StringProperty_AndThen_Desc_Int32Property() + { + var result = ApplyOrderByExpression(_catalogs.AsQueryable(), "Name, Id desc", _catalogRt); + result.Should().NotBeNull(); + var names = result.Select(c => c.Name); + var expected = _catalogs.OrderBy(c => c.Name).ThenByDescending(c => c.Id).Select(c => c.Name); + names.Should().Equal(expected); } diff --git a/MR3/Extensions/OData/tests/Castle.MonoRail.Extension.OData.Tests/ODataMetadataBuilderTestCase.cs b/MR3/Extensions/OData/tests/Castle.MonoRail.Extension.OData.Tests/ODataMetadataBuilderTestCase.cs index 231dcf46..ba5c0f85 100644 --- a/MR3/Extensions/OData/tests/Castle.MonoRail.Extension.OData.Tests/ODataMetadataBuilderTestCase.cs +++ b/MR3/Extensions/OData/tests/Castle.MonoRail.Extension.OData.Tests/ODataMetadataBuilderTestCase.cs @@ -143,7 +143,6 @@ public void SingleEntityWithComplexTypeProperty_BuildsResource() city.ResourceType.InstanceType.Should().Be(); } - [Test] public void TwoEntitiesNotRelated_BuildsTwoResources() { diff --git a/MR3/Extensions/OData/tests/Castle.MonoRail.Extension.OData.Tests/SegmentProcessorTestCase.EntitySet.cs b/MR3/Extensions/OData/tests/Castle.MonoRail.Extension.OData.Tests/SegmentProcessorTestCase.EntitySet.cs index b2bf9335..52f58be7 100644 --- a/MR3/Extensions/OData/tests/Castle.MonoRail.Extension.OData.Tests/SegmentProcessorTestCase.EntitySet.cs +++ b/MR3/Extensions/OData/tests/Castle.MonoRail.Extension.OData.Tests/SegmentProcessorTestCase.EntitySet.cs @@ -14,6 +14,15 @@ public partial class SegmentProcessorTestCase // naming convention for testing methods // [EntitySet|EntityType|PropSingle|PropCollection|Complex|Primitive]_[Operation]_[InputFormat]_[OutputFormat]__[Success|Failure] + private void ReadIdName(SyndicationItem item, out int id, out string name) + { + var xmlContent = (item.Content as XmlSyndicationContent).GetReaderAtContent(); + xmlContent.ReadStartElement("content", "http://www.w3.org/2005/Atom"); + xmlContent.ReadStartElement("properties", MetadataNs); + id = Int32.Parse( xmlContent.ReadElementString("Id", DataSvsNs) ); + name = xmlContent.ReadElementString("Name", DataSvsNs); + } + [Test] public void EntitySet_ViewWithFilter_Atom_Atom_Success() { @@ -26,15 +35,33 @@ public void EntitySet_ViewWithFilter_Atom_Atom_Success() Assertion.Callbacks.ViewSingleWasCalled(0); var item = feed.Items.ElementAt(0); - var xmlContent = (item.Content as XmlSyndicationContent).GetReaderAtContent(); - xmlContent.ReadStartElement("content", "http://www.w3.org/2005/Atom"); - xmlContent.ReadStartElement("properties", MetadataNs); - xmlContent.ReadElementString("Id", DataSvsNs); - var name = xmlContent.ReadElementString("Name", DataSvsNs); + int id; string name; + ReadIdName(item, out id, out name); name.Should().Be("Cat1"); } + [Test] + public void EntitySet_ViewWithOrderBy_Atom_Atom_Success() + { + Process("/catalogs/", SegmentOp.View, _model, qs: "$orderby=Name desc"); + + var feed = SyndicationFeed.Load(XmlReader.Create(new StringReader(_body.ToString()))); + feed.Items.Should().HaveCount(2); + + Assertion.Callbacks.ViewManyWasCalled(1); + Assertion.Callbacks.ViewSingleWasCalled(0); + + var item = feed.Items.ElementAt(0); + int id1; string name1; + ReadIdName(item, out id1, out name1); + item = feed.Items.ElementAt(1); + int id2; string name2; + ReadIdName(item, out id2, out name2); + + Assert.IsTrue(name1.CompareTo(name2) > 0); + } + [Test] public void EntitySet_View_Atom_Atom_Success() {