missing VisitGroupByClause in QueryModelVisitorBase

46 views
Skip to first unread message

raoof hojat

unread,
Oct 5, 2014, 3:23:57 PM10/5/14
to re-moti...@googlegroups.com
hi

im using relinq to build iqueryable

target language is a sql-like syntax

i had no problem until i need to implement group by clause that suddenly i see no base method in QueryModelVisitorBase for it
i debug it and see that handling groupby clause goes to VisitMainFromClause (if im not mistaken)
i think its bkaz sql handle aggregation in select part, but my target language have its group by syntax that can occur in any part of query(after where or even after order by)

is there any solution to this currently? if not is there any hope i can change the source of relinq to achive this?

best regards
raoof

Michael Ketting

unread,
Oct 6, 2014, 2:14:18 AM10/6/14
to re-moti...@googlegroups.com
Hello raoof!

Are you referring to how to handle group-by in re-linq the way we do it for T-SQL? If so, that's packaged away in the GroupResultOperator.

OTOH, the second part of your question looks like refers to something a bit more involved. It would be great if you could show us the linq and target-language syntax so we can talk about this with a little bit more context.

We also have a couple of integration tests for group-by here:
https://github.com/re-motion/Relinq-SqlBackend/blob/master/IntegrationTests.CSharp/LinqSamples101/GroupByHavingTests.cs

Best regards, Michael

Fabian Schmied

unread,
Oct 6, 2014, 3:01:57 AM10/6/14
to re-moti...@googlegroups.com
Hi raoof,

To me it seems you're actually only missing the "VisitGroupByClause" method, right? re-linq represents the GroupBy query operators as result operators (applied at the end of an ordinary query), which means that you should use the "VisitResultOperator" method to visit them.

E.g., consider the following query:

    orders.GroupBy (o => o.Customer)

This will result in a QueryModel with one MainFromClause, one SelectClause and one GroupResultOperator. For the latter, the VisitResultOperator method will be visited once.

Regarding GroupBy in a MainFromClause, consider the following query:

    orders.GroupBy (o => o.Customer).Select (g => g.Count())

This is equivalent to:

from g in (orders.GroupBy (o => o.Customer))
select g.Count()

Therefore, re-linq puts the "(orders.GroupBy (o => o.Customer))" part into a subquery. This means that the MainFromClause of the outer query contains a SubQueryExpression as its FromExpression. To deal with such subqueries, you need to override the "VisitSubQueryExpression" method in you expression visitor (or just check whether the FromExpression is a SubQueryExpression).

Best regards,
Fabian


--
You received this message because you are subscribed to the Google Groups "re-motion Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to re-motion-use...@googlegroups.com.
To post to this group, send email to re-moti...@googlegroups.com.
Visit this group at http://groups.google.com/group/re-motion-users.
For more options, visit https://groups.google.com/d/optout.

raoof hojat

unread,
Oct 6, 2014, 5:28:57 AM10/6/14
to re-moti...@googlegroups.com

thanks guys,

fabian i worte a simple target query with its linq syntax to show better:

[linq syntax]
from p in query<Person>()
group p by p.age into g
select new
 {
      key = g.Key,
      persons = g
 };

[target language(Arangodb aql syntax)]
for p in in Person
collect key=p.age into g
return
{
    key:key,
    persons:g
}

in this example VisitMainFromClause fromClause have the group by part, and VisitResultOperator is not called,
is it possible to have seprate VisitGroupByClause? so i can generate target language without handling group by in other parts(MainFromClause or ResultOperator)

another usecase is for "let":

from p in query<Person>()
let numbers= from n in p.numbers select n.numbers
select
{
person=p,
numbers=numbers
}
============target language===============
for p in Person
let numbers=for n in p.numbers
select
{
p:p,
numbers:numbers
}

i need to generate let in target language but relinq remove let and move it to where it is used and generate this instead

for p in Person
select
{
p:p,
numbers:(for n in p.numbers)
}

it means if user use "numbers" twice, generated target language cant benefit from its "let" syntax to reduce the compution of "numbers"

Fabian Schmied

unread,
Oct 7, 2014, 3:29:01 AM10/7/14
to re-moti...@googlegroups.com
(Answers inline.)

On Mon, Oct 6, 2014 at 11:28 AM, raoof hojat <ra0...@gmail.com> wrote:

thanks guys,

fabian i worte a simple target query with its linq syntax to show better:

[linq syntax]
from p in query<Person>()
group p by p.age into g
select new
 {
      key = g.Key,
      persons = g
 };

[target language(Arangodb aql syntax)]
for p in in Person
collect key=p.age into g
return
{
    key:key,
    persons:g
}

in this example VisitMainFromClause fromClause have the group by part, and VisitResultOperator is not called,

Right. re-linq represents the first part of the query (before the "into") as a subquery. It is held by a SubQueryExpression within the MainFromClause's FromExpression property.

Why does re-linq do it that way ?

First, the reason is that "into" in C# is not directly expressed in the compiled code. This is more or less what re-linq sees:

    query<Person>().GroupBy(p => p.age, p => p).Select(g => new { key = g.Key, persons = g })

Note that this is exactly the same as if you had written the following query:

    from g in (
        from p in query<Person>()
        group p by p.age
    )
    select new
    {
        key = g.Key,
        persons = g
   }

This means that re-linq cannot differentiate if the user wrote "into" ("group ... into g") or a subquery ("from g in (... group ...)") - it looks exactly the same after the C# compiler rewrote the query.

When we originally designed re-linq, we had to make a decision about how to represent these "into" constructs. (Note that "into" cannot only stand after a "group", you can also write "from p in ... select p into g".) We chose the subquery representation because:

- it fits more naturally into the "from where select" query model - I didn't really like the idea of having an IntoClause after the SelectClause, which can again be followed by body clauses and an additional SelectClause [you can simulate this in your QueryModelVisitor yourself, see below!];
- it is symmetric to the same scenario with AdditionalFromClauses (e.g., "from x in ... from g in (... group ...)") - by always representing it as a subquery, we only needed one concept instead of two concepts.

is it possible to have seprate VisitGroupByClause? so i can generate target language without handling group by in other parts(MainFromClause or ResultOperator)

Actually, it's not a matter of a GroupByClause, but a matter of an IntoClause. Not the "group ... by ..." is causing that problem, it's the "into".

For your scenario, you have two options: Either support subqueries (by implementing VisitSubQueryExpression in your expression visitor), which is hard, or simply "inline" the subquery, which is easier:

override VisitMainFromClause (MainFromClause c)
{
  var subQueryExpression = c.FromExpression as SubQueryExpression;
  if (subQueryExpression != null)
  {
    // "into" scenario! Visit all the sub query's clauses as if they were instead of this MainFromClause. Note that this means that VisitSelectClause can be called after VisitResultOperator.
    subQueryExpression.QueryModel.Accept (this);
  }
}

Using this snippet, VisitResultOperator should be called for the GroupBy clause in the right place.
 
another usecase is for "let":

from p in query<Person>()
let numbers= from n in p.numbers select n.numbers
select
{
person=p,
numbers=numbers
}
============target language===============
for p in Person
let numbers=for n in p.numbers
select
{
p:p,
numbers:numbers
}

i need to generate let in target language but relinq remove let and move it to where it is used and generate this instead

Yes, this is an open feature request: https://www.re-motion.org/jira/browse/RMLNQ-10. I'd be very happy if someone implemented this :)
Note that there are some open questions about this feature - if you tackle this, be sure to talk to Michael Ketting about them.

Best regards,
Fabian
Reply all
Reply to author
Forward
0 new messages