Skip to content

Commit

Permalink
Completed support for $orderby
Browse files Browse the repository at this point in the history
  • Loading branch information
hammett committed May 8, 2012
1 parent ed1c8dd commit 22ff797
Show file tree
Hide file tree
Showing 6 changed files with 133 additions and 63 deletions.
Expand Up @@ -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
Expand All @@ -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")
Expand Down Expand Up @@ -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<T,bool>
let build_linq_exp_predicate<'a> (paramType:Type) (ast:QueryAst) =
let rootExp, parameter = build_linq_exp_tree paramType ast
Expression.Lambda(rootExp, [parameter]) :?> Expression<Func<'a, bool>>

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<T,R>
let build_linq_exp_memberaccess<'a> (paramType:Type) (ast:QueryAst) =
let rootExp, parameter = build_linq_exp_tree paramType ast
Expression.Lambda(rootExp, [parameter]) :?> Expression<Func<'a, 'b>>

*)

let typed_queryable_filter<'a> (source:IQueryable) (ast:QueryAst) : IQueryable =
let typedSource = source :?> IQueryable<'a>
Expand All @@ -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<Queryable>, 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


Expand Up @@ -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
Expand Down
Expand Up @@ -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

Expand Up @@ -46,15 +46,63 @@ public void Init()
return AstLinqTranslator.build_linq_exp_predicate<T>(typeof(T), tree);
}

private Expression<Func<T, bool>> BuildOrderByExpression<T>(string expression, ResourceType rt)
private static IQueryable<T> ApplyOrderByExpression<T>(IQueryable<T> 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<T>(source, tree) as IQueryable<T>;
}

[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<T>(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);
}


Expand Down
Expand Up @@ -143,7 +143,6 @@ public void SingleEntityWithComplexTypeProperty_BuildsResource()
city.ResourceType.InstanceType.Should().Be<string>();
}


[Test]
public void TwoEntitiesNotRelated_BuildsTwoResources()
{
Expand Down
Expand Up @@ -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()
{
Expand All @@ -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()
{
Expand Down

0 comments on commit 22ff797

Please sign in to comment.