Some Eclipse Foundation services are deprecated, or will be soon. Please ensure you've read this important communication.

Bug 325684

Summary: QueryHints.BATCH combined with QueryHints.FETCH_GROUP_LOAD will cause NPE
Product: z_Archived Reporter: Frank Schwarz <fs5>
Component: EclipselinkAssignee: Nobody - feel free to take it <nobody>
Status: CLOSED FIXED QA Contact:
Severity: major    
Priority: P3 CC: andrei.ilitchev, michael.braeuer, peter.krogh, tom.ware
Version: unspecified   
Target Milestone: ---   
Hardware: All   
OS: All   
Whiteboard: submitted_patch
Attachments:
Description Flags
testcase
none
proposed patch
peter.krogh: iplog+
updated patch none

Description Frank Schwarz CLA 2010-09-18 13:44:04 EDT
Build Identifier: Trunk

If you combine QueryHints.FETCH_GROUP_LOAD and QueryHints.BATCH you will get this NPE:

java.lang.NullPointerException
	at org.eclipse.persistence.internal.sessions.AbstractSession$1.iterate(AbstractSession.java:4076)
	at org.eclipse.persistence.internal.descriptors.DescriptorIterator.startIterationOn(DescriptorIterator.java:666)
	at org.eclipse.persistence.internal.sessions.AbstractSession.load(AbstractSession.java:4105)
	at org.eclipse.persistence.queries.ObjectLevelReadQuery.executeDatabaseQuery(ObjectLevelReadQuery.java:1087)
	at org.eclipse.persistence.queries.DatabaseQuery.execute(DatabaseQuery.java:768)
	at org.eclipse.persistence.queries.ObjectLevelReadQuery.execute(ObjectLevelReadQuery.java:1038)
	at org.eclipse.persistence.queries.ReadAllQuery.execute(ReadAllQuery.java:383)
	at org.eclipse.persistence.queries.ObjectLevelReadQuery.executeInUnitOfWork(ObjectLevelReadQuery.java:1116)
	at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.internalExecuteQuery(UnitOfWorkImpl.java:2943)
	at org.eclipse.persistence.internal.sessions.AbstractSession.executeQuery(AbstractSession.java:1301)
	at org.eclipse.persistence.internal.sessions.AbstractSession.executeQuery(AbstractSession.java:1283)
	at org.eclipse.persistence.mappings.CollectionMapping.executeBatchQuery(CollectionMapping.java:842)
	at org.eclipse.persistence.mappings.ForeignReferenceMapping.extractResultFromBatchQuery(ForeignReferenceMapping.java:503)
	at org.eclipse.persistence.mappings.CollectionMapping.extractResultFromBatchQuery(CollectionMapping.java:823)
	at org.eclipse.persistence.internal.indirection.BatchValueHolder.instantiate(BatchValueHolder.java:55)
	at org.eclipse.persistence.internal.indirection.QueryBasedValueHolder.instantiate(QueryBasedValueHolder.java:75)
	at org.eclipse.persistence.internal.indirection.DatabaseValueHolder.getValue(DatabaseValueHolder.java:84)
	at org.eclipse.persistence.internal.indirection.UnitOfWorkValueHolder.instantiateImpl(UnitOfWorkValueHolder.java:160)
	at org.eclipse.persistence.internal.indirection.UnitOfWorkValueHolder.instantiate(UnitOfWorkValueHolder.java:220)
	at org.eclipse.persistence.internal.indirection.DatabaseValueHolder.getValue(DatabaseValueHolder.java:84)
	at org.eclipse.persistence.indirection.IndirectList.buildDelegate(IndirectList.java:237)
	at org.eclipse.persistence.indirection.IndirectList.getDelegate(IndirectList.java:397)
	at org.eclipse.persistence.indirection.IndirectList.size(IndirectList.java:726)
	at org.eclipse.persistence.internal.queries.CollectionContainerPolicy.sizeFor(CollectionContainerPolicy.java:177)
	at org.eclipse.persistence.internal.indirection.TransparentIndirectionPolicy.instantiateObject(TransparentIndirectionPolicy.java:363)
	at org.eclipse.persistence.mappings.ForeignReferenceMapping.instantiateAttribute(ForeignReferenceMapping.java:914)
	at org.eclipse.persistence.internal.sessions.AbstractSession$1.iterate(AbstractSession.java:4090)
	at org.eclipse.persistence.internal.descriptors.DescriptorIterator.startIterationOn(DescriptorIterator.java:666)
	at org.eclipse.persistence.internal.sessions.AbstractSession.load(AbstractSession.java:4102)
	at org.eclipse.persistence.queries.ObjectLevelReadQuery.executeDatabaseQuery(ObjectLevelReadQuery.java:1087)
	at org.eclipse.persistence.queries.DatabaseQuery.execute(DatabaseQuery.java:768)
	at org.eclipse.persistence.queries.ObjectLevelReadQuery.execute(ObjectLevelReadQuery.java:1038)
	at org.eclipse.persistence.queries.ReadAllQuery.execute(ReadAllQuery.java:383)
	at org.eclipse.persistence.queries.ObjectLevelReadQuery.executeInUnitOfWork(ObjectLevelReadQuery.java:1116)
	at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.internalExecuteQuery(UnitOfWorkImpl.java:2943)
	at org.eclipse.persistence.internal.sessions.AbstractSession.executeQuery(AbstractSession.java:1301)
	at org.eclipse.persistence.internal.sessions.AbstractSession.executeQuery(AbstractSession.java:1283)
	at org.eclipse.persistence.internal.sessions.AbstractSession.executeQuery(AbstractSession.java:1257)
	at org.eclipse.persistence.internal.jpa.EJBQueryImpl.executeReadQuery(EJBQueryImpl.java:479)
	at org.eclipse.persistence.internal.jpa.EJBQueryImpl.getResultList(EJBQueryImpl.java:714)
	at user.test.BatchFetchTest.queryWithEagerBatchFetch(BatchFetchTest.java:98)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
	at java.lang.reflect.Method.invoke(Method.java:597)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
	at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:28)
	at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:31)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:76)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:236)
	at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:49)
	at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)

Source is:
	EntityManager em = emf.createEntityManager();
	FetchGroup childFG = new FetchGroup();
	childFG.addAttribute("name");
		
	FetchGroup parentFG = new FetchGroup();
	parentFG.addAttribute("name");
	parentFG.addAttribute("children", childFG); 
//	parentFG.setShouldLoadAll(true);
		
	TypedQuery<Parent> query = em.createQuery("select p from Parent p", Parent.class);
	query.setHint(QueryHints.FETCH_GROUP, parentFG);
	query.setHint(QueryHints.FETCH_GROUP_LOAD, HintValues.TRUE);
	query.setHint(QueryHints.BATCH, "p.children");
		
	List<Parent> parents = query.getResultList();


Reproducible: Always

Steps to Reproduce:
A JUnit test case will be provided shortly
Comment 1 Frank Schwarz CLA 2010-09-18 13:50:47 EDT
Created attachment 179180 [details]
testcase

A JUnit test case
Comment 2 Frank Schwarz CLA 2010-09-18 13:58:14 EDT
Created attachment 179181 [details]
proposed patch
Comment 3 Andrei Ilitchev CLA 2010-09-20 15:48:18 EDT
Created attachment 179276 [details]
updated patch

The updated patch fixes the same flaw as the original patch, but in a different place: AbstractSession#load should not be passed ComplexQueryResult, it's responsibility of the query to handle ComplexResultQuery case and extract the actual result from it to pass to load method.
Added a test new simpleFetchGroupLoadWithBatch test case to both fieldaccess.fetchgroups.NestedFetchGroupTests and fetchgroups.NestedFetchGroupTests.

Apart from verifying that NPE is no longer thrown the test case verifies that all the loading is done during query.getResultList.

The test case is (almost) exact copy of the one suggested by Frank in e-mail:
Frank Schwarz wrote:
> > 
> > Hi,
> > 
> > I've been investigating the new fetch-group feature(s) of EL 2.1.0 and
> > there is something I'd like to understand:
> > 
> > Lets say you have got "Employee" -- "Project" (many to many)
> > 
> > FetchGroup projectGroup = new FetchGroup();
> > projectGroup.addAttribute("name");
> > 
> > FetchGroup employeeGroup = new FetchGroup();
> > employeeGroup.addAttribute("id");
> > employeeGroup.addAttribute("name");
> > employeeGroup.addAttribute("projects", projectGroup);
> > 
> > TypedQuery<Employee> query = em.createQuery("select e from ...",
> > Employee.class);
> > query.setHint(QueryHints.FETCH_GROUP, employeeGroup);
> > query.setHint(QueryHints.BATCH, "e.projects");
> > 
> > List<Employee> emps = query.getResultList(); // (1)
> > for (Employee e : emps) {
> >   e.getProjekte().size(); // (2)
> > }
> > 
> > You can observe two sql statements. This first one at position (1) to load
> > all employees and the second one at position (2) to load all projects of
> > all previously loaded employees.
> > 
> > I would have expected the second sql statement to occur also at position
> > (1). I thought that this fetch plan was saying: Please ensure that all
> > employees and all their projects are loaded when you return from
> > "getResultList() and load all projects with a separate batch statement."
> > 
> > Would the following statement change the behavior to the one I am
> > expecting?:
> > 
> > employeeGroup.setShouldLoadAll(true);
> > 
> > And, is batch fetching compatible with this setting?
> > 
> > 
> > Thank you for your help,
> > Frank
> > 
> > 
> > PS: It is currently not possible to simply test it, as it will raise a NPE
> > at
> > org.eclipse.persistence.internal.sessions.AbstractSession$1.iterate(AbstractSession.java:4067)
> > 
> >
Comment 4 Andrei Ilitchev CLA 2010-09-21 12:00:08 EDT
Checked into both trunk (2.2) and 2.1.2. Reviewed by James.
Comment 5 Peter Krogh CLA 2010-12-08 14:28:02 EST
Moving ipLog flag to patch
Comment 6 Eclipse Webmaster CLA 2022-06-09 10:23:44 EDT
The Eclipselink project has moved to Github: https://github.com/eclipse-ee4j/eclipselink