Skip to content

NH-3596 - DistinctRootEntityResultTransformer breaks Cache #383

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

Closed
wants to merge 6 commits into from
Closed

NH-3596 - DistinctRootEntityResultTransformer breaks Cache #383

wants to merge 6 commits into from

Conversation

rjperes
Copy link
Member

@rjperes rjperes commented Nov 26, 2014

https://nhibernate.jira.com/browse/NH-3596 (duplicate of https://nhibernate.jira.com/browse/NH-2867, #1243)
Fix for QueryOver and unit test
HQL still not working

@rjperes rjperes closed this Nov 26, 2014
Added HQL unit test
@rjperes rjperes reopened this Nov 26, 2014
@hazzik
Copy link
Member

hazzik commented Nov 27, 2014

@rjperes, can you please add test for case with Fetch(xxx).Eager ?

Also
CachingWithTransformerTests.UsingHqlToFutureWithCacheAndTransformerDoesntThrow
http://teamcity.codebetter.com/viewLog.html?buildId=138537&buildTypeId=bt7&guest=1#testNameId2115394835934985140
test is failing.

@rjperes
Copy link
Member Author

rjperes commented Nov 27, 2014

Didn't you read what I wrote? I said that I only fixed QueryOver, not HQL. And I copied existing tests, didn't create new ones.

@hazzik
Copy link
Member

hazzik commented Nov 27, 2014

Of course I did not read JIRA. Actually I did so, but after I wrote my
comment

@rjperes
Copy link
Member Author

rjperes commented Nov 27, 2014

I thought the solution would be the same, but it wasn't... trickier, I think...

@hazzik
Copy link
Member

hazzik commented Nov 27, 2014

There is one place you can change to get the same error as with QueryOver

QueryLoader.cs L#333

[Test]
public void UsingHqlToFutureWithCacheAndTransformerDoesntThrow()
{
//NH-3596
Copy link
Member

Choose a reason for hiding this comment

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

This is NH-2867

Copy link
Member Author

Choose a reason for hiding this comment

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

It's the same problem

@hazzik
Copy link
Member

hazzik commented Nov 27, 2014

BTW, for HQL it works completely incorrect. It returns 2 roles: null and Role
With commented result transformer in second query it throws InvalidCastException object[] -> Role

@rjperes
Copy link
Member Author

rjperes commented Nov 27, 2014

Yes, I know...

@hazzik
Copy link
Member

hazzik commented Nov 27, 2014

It seems that the problem is with self-referenced entities, and not with your solution. Can you adjust tests to use non-recursive entities?

@rjperes
Copy link
Member Author

rjperes commented Nov 27, 2014

But why, if this has to be fixed as well?

@rjperes
Copy link
Member Author

rjperes commented Nov 27, 2014

"not with your solution"?

@hazzik
Copy link
Member

hazzik commented Nov 27, 2014

Ok, just forget. I'm talking bullshit.

@rjperes
Copy link
Member Author

rjperes commented Nov 27, 2014

:-D
No, you are not! You must be sleepy, though... go to bed!!

@hazzik
Copy link
Member

hazzik commented Nov 27, 2014

Ok.... nevermind... We need following tests to cover all the cases:

MultiCriteria/MultiQueryOver
Criteria/QueryOver + Future
MulitQuery
Query + Future
Linq + Future

All of the above with and without cache
All of the above with self-reference and normal entities

@hazzik
Copy link
Member

hazzik commented Nov 27, 2014

So... what I'm talking about... following test query does not work at all

var roles = session
    .CreateQuery("from Role r left join r.Children left join r.Parent")
    .SetResultTransformer(Transformers.DistinctRootEntity)
    .List<Role>();

    Assert.AreEqual(5, result.Count); // always 2

@rjperes
Copy link
Member Author

rjperes commented Nov 27, 2014

Yes, I know! That's why I said it has an error! But it should work!

@hazzik
Copy link
Member

hazzik commented Nov 27, 2014

Not as part of solving this issue... The query does not use multiquery/future nor cache.

@rjperes
Copy link
Member Author

rjperes commented Nov 27, 2014

But where did that code come from? It's not in my commit, for sure.

@hazzik
Copy link
Member

hazzik commented Nov 27, 2014

I'm playing with your pull request. So I modified the query (removed multi-query and cahce) to see if this query works on it's own.

@rjperes
Copy link
Member Author

rjperes commented Nov 27, 2014

I have code like this in a project (NH 4.0.2):

var ordersInHql = session.CreateQuery("from Order o left join fetch o.Details d left join fetch d.Product").List();

And it works perfectly...

@hazzik
Copy link
Member

hazzik commented Nov 27, 2014

And it works perfectly...

Yes, Order-Details-Product works... But Role-Role-Role does not. And that's why I said this: "It seems that the problem is with self-referenced entities", "All of the above with self-reference and normal entities"

@rjperes
Copy link
Member Author

rjperes commented Nov 27, 2014

Yes, you are right. I don't have much time ATM, maybe later, or if you can handle it, fine! BTW, congrats on the new job! ;-)

@hazzik
Copy link
Member

hazzik commented Nov 27, 2014

Thanks

{
//store values in cache
session
.CreateQuery("from Role r left join r.Children left join r.Parent where r.Parent is null")
Copy link
Member

Choose a reason for hiding this comment

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

should be

"select r from Role r left join r.Children left join r.Parent where r.Parent is null"

Copy link
Member Author

Choose a reason for hiding this comment

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

I know that it is different from the saved query, but it was like this in the unit test (QueryOver version).

@rjperes
Copy link
Member Author

rjperes commented Nov 27, 2014

TeamCity does that for you!! :-P

.SetCacheMode(CacheMode.Normal)
.Future<Role>();

//get values from cache
Copy link
Member

Choose a reason for hiding this comment

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

Are you sure that this will be from cache?

Copy link
Member Author

Choose a reason for hiding this comment

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

It should be, shouldn't it? It's the same query, no parameters...

Copy link
Member

Choose a reason for hiding this comment

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

Don't forget that they are future queries.

Copy link
Member

Choose a reason for hiding this comment

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

The query cache key will be

"select role0_.Id as Id0_0_, children1_.Id as Id0_1_, role2_.Id as Id0_2_, role0_.Name as Name0_0_, role0_.ParentId as ParentId0_0_, children1_.Name as Name0_1_, children1_.ParentId as ParentId0_1_, children1_.ParentId as ParentId0__, children1_.Id as Id0__, role2_.Name as Name0_2_, role2_.ParentId as ParentId0_2_ from Role role0_ left outer join Role children1_ on role0_.Id=children1_.ParentId left outer join Role role2_ on role0_.ParentId=role2_.Id where role0_.ParentId is null;\r\nselect role0_.Id as Id0_0_, children1_.Id as Id0_1_, role2_.Id as Id0_2_, role0_.Name as Name0_0_, role0_.ParentId as ParentId0_0_, children1_.Name as Name0_1_, children1_.ParentId as ParentId0_1_, children1_.ParentId as ParentId0__, children1_.Id as Id0__, role2_.Name as Name0_2_, role2_.ParentId as ParentId0_2_ from Role role0_ left outer join Role children1_ on role0_.Id=children1_.ParentId left outer join Role role2_ on role0_.ParentId=role2_.Id where role0_.ParentId is null;\r\n"

Which means that both queries' results will go to the same cache item...

Copy link
Member

Choose a reason for hiding this comment

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

In other words the first future is pending and not executed before the second one gets executed. So both queries (although being the same) ends-up batched together in a single round-trip to database. So it has nothing to retrieve from cache, since only one actual sql-query is performed.

The previous test has the same issue.

@hazzik
Copy link
Member

hazzik commented Nov 27, 2014

So you need revert last commits and call these methods twice

@hazzik
Copy link
Member

hazzik commented Nov 27, 2014

And then it brakes....

@rjperes
Copy link
Member Author

rjperes commented Nov 27, 2014

"Which means that both queries' results will go to the same cache item..."
That's the intention, isn't it?
But I think I spotted a problem in QueryParameter parameterless constructor: it is disabling cache by default (the bool cacheable parameter is set to false, and not with the value of cacheMode). This is in SessionImpl, line 606. So, in Loader, line 1525, it won't be use cache.

@hazzik
Copy link
Member

hazzik commented Nov 27, 2014

Ok, just revert last 2 commits, fix HQL queries and call each test method twice...

@hazzik
Copy link
Member

hazzik commented Nov 27, 2014

That's the intention, isn't it?

Yes, and... no...

@rjperes
Copy link
Member Author

rjperes commented Nov 27, 2014

I don't understand what you mean.
Right now, HQL is identical to QueryOver. In both cases, we issue a first future query that should be cached, and then we issue another one, and we execute it. Results are as expected. While debugging, I noticed that query cache is not being used because QueryParameters is initialized without the cache parameters (cacheable, cacheRegion).

@hazzik
Copy link
Member

hazzik commented Nov 27, 2014

we issue a first future query that should be cached, and then we issue another one, and we execute it.

No, it does not work as this... Because this is a future...

2 queries are combined together (it does not matter if they are identical or not), and after then they are treated as one single query. So they miss/hit cache together. So what I'm trying to say, that in your test you actually only put queries to cache, and do not retrieve them

See the code:

[Test]
public void UsingQueryOverToFutureWithCacheAndTransformerDoesntThrow()
{
    //store values in cache
    UsingQueryOverToFutureWithCacheAndTransformerDoesntThrowCore();
    //get values from cache
    UsingQueryOverToFutureWithCacheAndTransformerDoesntThrowCore();
}

private void UsingQueryOverToFutureWithCacheAndTransformerDoesntThrowCore()
{
//NH-3596
    using (var session = this.OpenSession())
    using (session.BeginTransaction())
    {
        session
            .QueryOver<Role>()
            .Where(x => x.Parent == null)
            .TransformUsing(Transformers.DistinctRootEntity)
            .Cacheable()
            .CacheMode(CacheMode.Normal)
            .Future(); // schedule query for execution

        Role children = null;
        Role parent = null;

        var roles = session
            .QueryOver<Role>()
            .Left.JoinAlias(x => x.Children, () => children)
            .Left.JoinAlias(x => x.Parent, () => parent)
            .TransformUsing(Transformers.DistinctRootEntity)
            .Cacheable()
            .CacheMode(CacheMode.Normal)
            .Future(); // schedule query for execution

        var result = roles.ToList(); // both queries executed here in one roundtrip

        Assert.AreEqual(5, result.Count);
    }
}

[Test]
public void UsingHqlToFutureWithCacheAndTransformerDoesntThrow()
{
    //NH-3596
    //store values in cache
    UsingHqlToFutureWithCacheAndTransformerDoesntThrowCore();
    //get values from cache
    UsingHqlToFutureWithCacheAndTransformerDoesntThrowCore();
}

private void UsingHqlToFutureWithCacheAndTransformerDoesntThrowCore()
{
    using (var session = this.OpenSession())
    using (session.BeginTransaction())
    {
        session
            .CreateQuery("select r from Role r left join r.Children left join r.Parent where r.Parent is null")
            .SetResultTransformer(Transformers.DistinctRootEntity)
            .SetCacheable(true)
            .SetCacheMode(CacheMode.Normal)
            .Future<Role>(); // schedule query for execution

        var roles = session
            .CreateQuery("select r from Role r left join r.Children left join r.Parent")
            .SetResultTransformer(Transformers.DistinctRootEntity)
            .SetCacheable(true)
            .SetCacheMode(CacheMode.Normal)
            .Future<Role>(); // schedule query for execution

        var result = roles.ToList(); // both queries executed here in one roundtrip

        Assert.AreEqual(5, result.Count);
    }
}

@rjperes
Copy link
Member Author

rjperes commented Nov 27, 2014

OK, but that doesn't affect my fix, and also the "bug" I spotted. My fix makes the unit test work, otherwise, it would fail.

@hazzik
Copy link
Member

hazzik commented Nov 27, 2014

Your fix fixes only half of the problem, only that part when you put results to cache, but does not fix the same problem when you retrieve from cache.

@rjperes
Copy link
Member Author

rjperes commented Nov 27, 2014

Great! So you'll only have half the trouble! :-)
BTW, do look at that problem I reported...

@hazzik
Copy link
Member

hazzik commented Nov 27, 2014

This is in SessionImpl, line 606

This is inside Find method which is private and used only from SessionImpl.Delete

@hazzik
Copy link
Member

hazzik commented Nov 27, 2014

Ok, back to our sheeps... I managed to fix QueryOver by application of exactly same changes to MultipleQueriesCacheAssembler.Assemble, and it promoted error in hql to different part...

@hazzik
Copy link
Member

hazzik commented Nov 27, 2014

I think that the best solution is split MultipleQueriesCacheAssembler to MultipleQueriesCacheAssemblerForMultiQuery and MultipleQueriesCacheAssemblerForCriteria

@hazzik
Copy link
Member

hazzik commented Nov 27, 2014

Also, please add tests with Order-Details-Product domain, because else branch is not tested..

(This one)

else
{
    singleQueryCached.Add(TypeHelper.Disassemble(valuesToCache, assemblersToCache, null, session, null));
}

@rjperes
Copy link
Member Author

rjperes commented Nov 27, 2014

"This is inside Find method which is private and used only from SessionImpl.Delete"
No, it isn't! While debugging the unit test, I land there!

@rjperes
Copy link
Member Author

rjperes commented Nov 27, 2014

"This is inside Find method which is private and used only from SessionImpl.Delete"
You are right, my fault! Forget it.

singleQueryCached.Add(assemblers[0].Disassemble(objToCache, session, owner));
}
else
if (objToCache != null)
Copy link
Member

Choose a reason for hiding this comment

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

If #1783 is merged, this fix will be fixing an obsoleted class.

The future case is maybe already no more failing or failing in another way, since it does no more use multi-query/multi-criteria. (See #1718.)

@fredericDelaporte
Copy link
Member

fredericDelaporte commented Jul 13, 2018

I have rebased this and amended tests for them to actually test caching then retrieving from cache. I have also rewritten them for using multi-query and multi-criteria, since Future does no more use it.

They fail, as expected by @hazzik.

I cannot push the rebase to show this, it seems "Allow edits from maintainers." is not enabled.

@fredericDelaporte
Copy link
Member

The affected APIs are now obsoleted, and the replacing ones do not suffer of the bug.

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

Successfully merging this pull request may close these issues.

3 participants