Skip to content

Combine Future queries for ICriteria and IQuery #1718

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

Merged
merged 4 commits into from
Jun 24, 2018

Conversation

bahusoid
Copy link
Member

@bahusoid bahusoid commented May 31, 2018

The intention is to implement batcher that can contain both ICriteria and IQuery based queries. And make Future implementation based on this batcher. See examples in tests

New batcher interface IQueryBatch (implementation QueryBatch ) allows only to Add queries wrapped in to IQueryBatchItem interface to batch and Execute batch.

See implementation for Criteria, IQuery and Linq. Most of functionality is shared in QueryBatchItemBase .

Caching is implemented per query (it's up to IQueryBatchItem implementation to cache or not to cache). So it's possible to cache queries with different settings (cache region etc), combine cached and non-cached queries. And batcher doesn't need to know all internal details about query cache (so it might be different cache for different IQueryBatchItem implementations).

Fixes #752

As "side effects" of this re-implementation of futures:

@hazzik
Copy link
Member

hazzik commented Jun 1, 2018

I want you to confirm that you are OK in principle.

I'm OK.

@fredericDelaporte
Copy link
Member

I am also ok on the principle, provided this is not overly complex to achieve. (I have not looked yet at how you do that.)

@fredericDelaporte

This comment has been minimized.

@bahusoid

This comment has been minimized.

@bahusoid bahusoid force-pushed the futureMerge2 branch 3 times, most recently from e93cd5d to 1f59d46 Compare June 3, 2018 18:58
@bahusoid
Copy link
Member Author

bahusoid commented Jun 3, 2018

@hazzik , @fredericDelaporte Could you please review my changes in current state.

I got rid of auto-flushing in AbstractQueryImpl.GetTranslators. Really strange and unexpected place - retrieving query metadata should not lead to flush. And why to do check for each query in batch - it should be done once per batch execution. I also fixed #865

I've enabled batcher for stateless session. Seems to be working fine.

Should I obsolete existing future batcher with all related classes?

@hazzik
Copy link
Member

hazzik commented Jun 4, 2018

@bahusoid I just wanted to suggest to make additions as a separate commits so it would be easier to see what's changed.

@fredericDelaporte

This comment has been minimized.

@fredericDelaporte

This comment has been minimized.

@bahusoid
Copy link
Member Author

bahusoid commented Jun 4, 2018

In fact IsUnifiedFuture setting has to be kept

Actually I don't think we need it.

We expose old future as:

FutureCriteriaBatch FutureCriteriaBatch { get; }
FutureQueryBatch FutureQueryBatch { get; }

Where both FutureCriteriaBatch and FutureQueryBatch hard-wired to SessionImpl class via protected base ctor:

protected FutureBatch(SessionImpl session)
{
this.session = session;
}

And that's why old future is disabled for stateless session.

So we shouldn't bother to fallback to old code - old code can work only with SessionImpl.

@bahusoid
Copy link
Member Author

bahusoid commented Jun 4, 2018

I just wanted to suggest to make additions as a separate commits
A separated commit for separated subjects would be nice too

Ok. I will rewrite history in few commits. All changes are specific for this feature (and multi criteria fix is trivial after required for this change refactoring).

Enabling batcher for stateless session would also better have its own commit.

As already said in previous comment - it's supported only by new batcher.

@@ -116,15 +116,5 @@ public abstract partial class AbstractQueryImpl2 : AbstractQueryImpl
After();
}
}

protected internal override async Task<IEnumerable<ITranslator>> GetTranslatorsAsync(ISessionImplementor sessionImplementor, QueryParameters queryParameters, CancellationToken cancellationToken)
{
Copy link
Member

Choose a reason for hiding this comment

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

Same as previous comment.

@@ -105,21 +105,5 @@ public override Task<IEnumerable<T>> EnumerableAsync<T>(CancellationToken cancel
After();
}
}

protected internal override Task<IEnumerable<ITranslator>> GetTranslatorsAsync(ISessionImplementor sessionImplementor, QueryParameters queryParameters, CancellationToken cancellationToken)
Copy link
Member

Choose a reason for hiding this comment

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

Same as previous comment.

@@ -35,6 +35,11 @@ internal static IDisposable BeginProcess(this ISessionImplementor session)
// breaking change in case in custom session implementation is used.
new SessionIdLoggingContext(session.SessionId);
}

internal static IMultiAnyQueryBatch GetFutureMultiBatch(this ISessionImplementor session)
Copy link
Member

Choose a reason for hiding this comment

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

If obsoleting the previous batcher, better name the new one even more generically. Something like GetFutureBatch here.

@@ -903,12 +903,20 @@ public IQuery SetResultTransformer(IResultTransformer transformer)

public IFutureEnumerable<T> Future<T>()
{
if (FutureSettings.IsUnifiedFuture)
Copy link
Member

Choose a reason for hiding this comment

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

As said in a standalone comment, this should be instead some checking on the session capabilities (a type test indeed), likely done in the constructor of the query class.

/// <summary>
/// Base class for both ICriteria and IQuery queries
/// </summary>
public abstract partial class MultiAnyQueryBase<TResult> : IMultiAnyQuery, IMultiAnyQuery<TResult>
Copy link
Member

Choose a reason for hiding this comment

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

IMultiAnyQuery already comes with IMultiAnyQuery<TResult>, reduce redundancy. An same remark than previously for naming.


namespace NHibernate
{
public partial class MultiAnyQuery<TResult> : MultiAnyQueryBase<TResult>, IMultiAnyQuery<TResult>
Copy link
Member

Choose a reason for hiding this comment

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

Same remarks as previously on naming and interfaces.

var count = _queryInfos.Count;
NewArray(count, out _hydratedObjects);
NewArray(count, out _subselectResultKeys);
NewArray(count, out _loaderResults);
Copy link
Member

Choose a reason for hiding this comment

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

Why not just writing regular code here?

_hydratedObjects = new List<object>[count];
_subselectResultKeys = new List<EntityKey[]>[count];
_loaderResults = new IList[count];

Adding a general purpose NewArray in that class just for this need here looks as a code smell to me. Either remove it or put that as an internal helper on a class like ArrayHelper.

return rowCount;
};

_loaderResults[index] = tmpResults;
Copy link
Member

Choose a reason for hiding this comment

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

Putting this after the yield leaves the state a bit inconsistent between each MoveNext when enumerating. Better move that before the yield.

/// <summary>
/// Universal query batcher
/// </summary>
public partial class MultiAnyQueryBatch : IMultiAnyQueryBatch
Copy link
Member

Choose a reason for hiding this comment

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

Again, same remark for naming than on the interface.

@bahusoid
Copy link
Member Author

bahusoid commented Jun 5, 2018

I've split commit with some squashed clean up in tests.

Regarding naming for IMultiAnyQueryBatch, IMultiAnyQuery and related classes. We should choose new best pair :)

  1. IQueryBatch and IAnyQuery
  2. IAnyQueryBatch and IAnyQuery
  3. IQueryBatch and IQueryBatchItem
  4. Your suggestion

public interface INhQueryProviderSupportMultiBatch
{
IQuery GetPreparedQuery(Expression exrpession, out NhLinqExpression expression);
IMultiAnyQueryBatch GetFutureMultiBatch();
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'm thinking to expose ISessionImplementor Session instead. And get batch from session. Any objections?

Copy link
Member

Choose a reason for hiding this comment

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

I do not really see a benefit in exposing the whole session from the Linq provider. I would rather keep just exposing the future batch. Or even cease exposing it and add methods for adding new Linq query to it, a bit like the old interface was doing.

Copy link
Member Author

Choose a reason for hiding this comment

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

The benefit is to make further modifications simpler. If session was available and old future was using it - we wouldn't need to introduce new interface. It was only internal modifications in extension methods. Is Session really internal implementation detail for provider? I guess it can be abstracted one more layer down - but it looks to me as very rare breaking change (and such modification will be breaking in many more places).

Copy link
Member

Choose a reason for hiding this comment

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

Ok, go for it.

@fredericDelaporte

This comment has been minimized.

@fredericDelaporte

This comment has been minimized.

@bahusoid
Copy link
Member Author

bahusoid commented Jun 5, 2018

Will it be a big change to make them use the same query batcher than futures, allowing this way to only keep one non-obsoleted batcher? And it would also reduce the amount of code in which we have bugs to fix!

I was going to look in to it when new batcher is ready. But I suspect - yes. Are you OK with using new batcher cache approach in MultiCriteria\MultiQuery?

@fredericDelaporte
Copy link
Member

Better have a single way of tackling the same subject everywhere, but let see that in another PR.

@@ -2419,14 +2425,15 @@ public static IFutureEnumerable<TSource> ToFuture<TSource>(this IQueryable<TSour
/// <exception cref="T:System.NotSupportedException"><paramref name="source" /> <see cref="IQueryable.Provider"/> is not a <see cref="INhQueryProvider"/>.</exception>
public static IFutureValue<TSource> ToFutureValue<TSource>(this IQueryable<TSource> source)
{
if (FutureSettings.IsUnifiedFuture)
if(source.Provider is INhQueryProviderSupportMultiBatch batchProvider)
Copy link
Member

Choose a reason for hiding this comment

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

Missing space after if.

@@ -2404,8 +2404,14 @@ public static Task<List<TSource>> ToListAsync<TSource>(this IQueryable<TSource>
/// <exception cref="T:System.NotSupportedException"><paramref name="source" /> <see cref="IQueryable.Provider"/> is not a <see cref="INhQueryProvider"/>.</exception>
public static IFutureEnumerable<TSource> ToFuture<TSource>(this IQueryable<TSource> source)
{
if(source.Provider is INhQueryProviderSupportMultiBatch batchProvider)
Copy link
Member

Choose a reason for hiding this comment

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

Missing space after if.

public interface INhQueryProviderSupportMultiBatch
{
IQuery GetPreparedQuery(Expression exrpession, out NhLinqExpression expression);
IMultiAnyQueryBatch GetFutureMultiBatch();
Copy link
Member

Choose a reason for hiding this comment

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

I do not really see a benefit in exposing the whole session from the Linq provider. I would rather keep just exposing the future batch. Or even cease exposing it and add methods for adding new Linq query to it, a bit like the old interface was doing.

@fredericDelaporte

This comment has been minimized.

@bahusoid
Copy link
Member Author

bahusoid commented Jun 6, 2018

Looking at existing Future issues:

  1. NH-3350 - Duplicate records using Future() #1293 - NH-3350 - Duplicate records using Future()
    To fix it Loader.InitializeEntitiesAndCollections needs to be called right after query is loaded.
    Fixed in 1ac0ad5

  2. NH-3835 - Future/MultiCriteria 2nd level caching #840 - NH-3835 - Future/MultiCriteria 2nd level caching
    Fixed for Future.

  3. NH-3334 - 'collection is not associated with any session' upon refreshing objects from QueryOver<>().Future<>() #1290 - NH-3334 - 'collection is not associated with any session' upon refreshing objects from QueryOver<>().Future<>()
    To fix it Session.PersistenceContext.InitializeNonLazyCollections(); needs to be called (in QueryBatchItemBase.PostProcess or maybe once per batch)
    Not sure if it's a safe change. As before - all eager mappings were loaded lazly by Future. Wasn't it intentional behavior?

  4. NH-2901 - Multiple .Fetch() calls within Future query results in collection that does not persist deletes #1249 - NH-2901 - Multiple .Fetch() calls within Future query results in collection that does not persist deletes
    Unclear ticket. He either tries to fetch multiple bags in one query which throws exception (maybe missing delete due to wrong exception handling?)
    Or tries to fetch not mapped collection - which is no op and doesn't affect behavior in any way (double checked it).

  5. NH-3541 - Future queries of Criteria API/QueryOver are batched separately from other query methods #752 - NH-3541 - Multiple Queries of Criteria API/QueryOver are batched separately from other query methods
    Fixed

  6. NH-3864 - Cacheable Multicriteria/Future'd query with aliased join throw exception #1344 NH-3864 - Cacheable Multicriteria/Future'd query with aliased join throw exception
    Fixed for Future

@bahusoid bahusoid force-pushed the futureMerge2 branch 2 times, most recently from a8a01f0 to 4432f97 Compare June 6, 2018 08:02
@fredericDelaporte fredericDelaporte added this to the 5.2 milestone Jun 12, 2018
@hazzik
Copy link
Member

hazzik commented Jun 20, 2018

Is this done? Or more work required apart from squashing commits?

@fredericDelaporte
Copy link
Member

From my point of view, yes. But since the last commit is some refactoring I have made, I was expecting a feedback from @bahusoid, before approving.

I am already thinking of a follow-up PR which would obsolete the current multi-query/criteria for the query batcher introduced here. I do not think it should be done directly here, because this PR is already quite big, and its main subject was futures.

@bahusoid
Copy link
Member Author

@fredericDelaporte I have no objections with your latest changes. And I also think it's ready.

@fredericDelaporte
Copy link
Member

Rebased and squashed.

Copy link
Member

@fredericDelaporte fredericDelaporte left a comment

Choose a reason for hiding this comment

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

During my work on #1788, #1783 and #383, I realized that many points were wrong in the QueryBatcher initial implementation.
#1788 was fixing some of them by the way, but I think that we instead need a dedicated PR for fixing them.

return;
}

using (Session.BeginProcess())

This comment was marked as resolved.

protected void DoExecute()
{
var resultSetsCommand = Session.Factory.ConnectionProvider.Driver.GetResultSetsCommand(Session);
CombineQueries(resultSetsCommand);

This comment was marked as resolved.

int rowCount = 0;
try
{
if (resultSetsCommand.HasQueries)

This comment was marked as resolved.

//Skip processing for items already loaded from cache
if (_queryInfos[i].CacheKey?.ResultTransformer != null && _loaderResults[i] != null)
{
_loaderResults[i] = loader.TransformCacheableResults(queryParameters, _queryInfos[i].CacheKey.ResultTransformer, _loaderResults[i]);

This comment was marked as resolved.


if (index == _queryInfos.Count - 1)
{
InitializeEntitiesAndCollections(reader, hydratedObjects);

This comment was marked as resolved.

This comment was marked as resolved.

This comment was marked as resolved.

This comment was marked as resolved.

This comment was marked as resolved.

for (int i = 0; i < _queryInfos.Count; i++)
{
_queryInfos[i].Loader.InitializeEntitiesAndCollections(
hydratedObjects[i], reader, Session, Session.PersistenceContext.DefaultReadOnly);

This comment was marked as resolved.

if (queryInfo.Cache != null)
{
queryInfo.Loader.PutResultInQueryCache(Session, queryInfo.Parameters, queryInfo.Cache, queryInfo.CacheKey, _loaderResults[i]);
}

This comment was marked as resolved.

This comment was marked as resolved.


using (Session.BeginProcess())
{
DoExecute();

This comment was marked as resolved.

fredericDelaporte added a commit to fredericDelaporte/nhibernate-core that referenced this pull request Jul 10, 2018
}
if (queryParameters.HasAutoDiscoverScalarTypes)
{
loader.AutoDiscoverTypes(reader, queryParameters, null);

This comment was marked as resolved.

fredericDelaporte added a commit to fredericDelaporte/nhibernate-core that referenced this pull request Jul 10, 2018
 * AutoFlush is handled too late, causing stale data to be yielded
 * Cacheable result transformation is not fully done
 * Auto discovery of types fails
 * InitializeEntitiesAndCollections may be wrongly skipped or called
 * DoExecute should be renamed ExecuteBatched
 * BeginProcess should span the whole processing

 Follow up to nhibernate#1718. See its reviews for more details on the above
 shortcomings.
fredericDelaporte added a commit that referenced this pull request Jul 11, 2018
 * AutoFlush is handled too late, causing stale data to be yielded
 * Cacheable result transformation is not fully done
 * Auto discovery of types fails
 * InitializeEntitiesAndCollections may be wrongly skipped or called
 * DoExecute should be renamed ExecuteBatched
 * BeginProcess should span the whole processing

 Follow up to #1718. See its reviews for more details on the above
 shortcomings.
@bahusoid bahusoid deleted the futureMerge2 branch February 21, 2019 17:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
4 participants