Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add cross join support for Hql and Linq query provider #2327

Merged
merged 10 commits into from
Mar 21, 2020

Conversation

maca88
Copy link
Contributor

@maca88 maca88 commented Mar 11, 2020

Fixes: #1128
Closes #1060

Hql example:

select s from EntityComplex s cross join EntityComplex q where s.SameTypeChild.Id = q.SameTypeChild.Id

Linq example:

from o in db.Orders
from o2 in db.Orders
where (o.OrderId == o2.OrderId + 1) || (o.OrderId == o2.OrderId - 1)
select new { o.OrderId, OrderId2 = o2.OrderId }

Linq query provider will use cross join when using multiple from statements and will fallback to an inner join with on 1=1, when the dialect does not support it.

Possible breaking change:

  • Custom dialects used for databases that do not support cross join, will have to override SupportsCrossJoin property and set it to false.

Copy link
Member

@bahusoid bahusoid left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and will fallback to an implicit join when the dialect does not support it.

Is this case tested by any CI configurations?
Maybe we should simulate cross join as inner join with bogus condition like 1 = 1. Otherwise queries like #2037 (comment) won't be possible on such dialects.

/// <summary>
/// Does this dialect support CROSS JOIN?
/// </summary>
public virtual bool SupportsCrossJoin => true;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't it be false by default? Otherwise it can be breaking change for some user custom dialects

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I took the same approach Hibernate did with getCrossJoinSeparator which enables cross join by default. If you think that is better to have it disabled by default I will do it, otherwise we can add it as a possible breaking change.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fine with

add it as a possible breaking change.

@bahusoid
Copy link
Member

bahusoid commented Mar 11, 2020

What about joins generated for associations used in expressions (something like where o.Association.PropertyId == 1). If I remember correctly they can also be generated as implicit joins. Shouldn't we also use cross joins instead in Linq queries?

@maca88
Copy link
Contributor Author

maca88 commented Mar 11, 2020

Is this case tested by any CI configurations?

No, I will try to update the tests to test both scenarios.

Maybe we should simulate cross join as inner join with bogus condition like 1 = 1

Why not, I will add this fallback.

What about joins generated for associations used in expressions (something like where o.Association.PropertyId == 1). If I remember correctly they are also generated as implicit joins. Shouldn't we also use cross joins instead in Linq queries?

They are still generated with an implicit join. I tried to use cross join for such scenarios but the code is quite messy and it requires a lot more work as I expected. There are preconditions that such joins always starts with a comma, for example:

// The FROM fragment will probably begin with ', '. Remove this if it is present.
if ( fromFragment.StartsWithCaseInsensitive( ", " ) ) {
fromFragment = fromFragment.Substring( 2 );
}

SqlString from = joins.ToFromFragmentString;
if (from.StartsWithCaseInsensitive(","))
{
from = from.Substring(1);
}
else if (from.StartsWithCaseInsensitive(" inner join"))
{
from = from.Substring(11);
}

.Append(join.ToFromFragmentString.Substring(2)) // remove initial ", "

which are also the root issue for the problem described in #1060:

I tried to make a similar change to QueryJoinFragment (I just replaced ", " with " cross join "), but for some reason it translated the HQL "from ObjectA a, ObjectB" into the SQL "from cross join TableA a cross join TableB b"

As this is not trivial to change, I decided to leave it for a separate PR.

@bahusoid
Copy link
Member

bahusoid commented Mar 11, 2020

As this is not trivial to change, I decided to leave it for a separate PR.

Ok. Got it. So to clarify - did you try to do it globally (both for LINQ and hql)? Won't it be simpler to do just for Linq as a start?

@maca88
Copy link
Contributor Author

maca88 commented Mar 12, 2020

did you try to do it globally (both for LINQ and hql)?

Yes, the issues that were mentioned are for implementing it globally.

Won't it be simpler to do just for Linq as a start?

First I have to correct myself, Linq already supports expression like where o.Association.PropertyId == 1, WhereJoinDetector detects all associations and add joins for them. Implicit joins are only added for expression that are within an on statement:

from c in db.Customers join o in db.Orders on c.ContactTitle equals o.Customer.ContactTitle

where an implicit join is added for o.Customer.ContactTitle. With the last commit, WhereJoinDetector is also used for on statements and a cross join or inner join (fallback) is generated instead.

Is this case tested by any CI configurations?

Now all cross join tests that I found in LinqQuerySamples and JoinTests are tested for both scenarios.

Maybe we should simulate cross join as inner join with bogus condition like 1 = 1

The fallback is now added.

@maca88
Copy link
Contributor Author

maca88 commented Mar 12, 2020

The recent commit also fixes #1128, added a test for that and updated the description.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment