This is an automated email from the ASF dual-hosted git repository. ntimofeev pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/cayenne.git
The following commit(s) were added to refs/heads/master by this push: new ab136bb43 CAY-2814 Select query iterator() and batchIterator() methods return incorrect results - major code cleanup and refactoring ab136bb43 is described below commit ab136bb436c222ac1f8408ba596abbac0c148d46 Author: stariy95 <stari...@gmail.com> AuthorDate: Wed Nov 8 16:55:35 2023 +0400 CAY-2814 Select query iterator() and batchIterator() methods return incorrect results - major code cleanup and refactoring --- .../org/apache/cayenne/access/DataContext.java | 3 +- .../cayenne/access/DataDomainQueryAction.java | 234 +++++++++------------ .../access/ResultIteratorConverterDecorator.java | 25 ++- .../apache/cayenne/util/IteratedQueryResponse.java | 65 ------ .../apache/cayenne/util/GenericResponseTest.java | 14 +- .../org/apache/cayenne/util/ListResponseTest.java | 15 +- 6 files changed, 137 insertions(+), 219 deletions(-) diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/DataContext.java b/cayenne-server/src/main/java/org/apache/cayenne/access/DataContext.java index 347eaf7bf..ec930c81a 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/access/DataContext.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/access/DataContext.java @@ -798,10 +798,9 @@ public class DataContext extends BaseContext { IteratedQueryDecorator queryDecorator = new IteratedQueryDecorator(query); Query queryToRun = nonNullDelegate().willPerformQuery(this, queryDecorator); QueryResponse queryResponse = onQuery(this, queryToRun); - return (ResultIterator<T>) queryResponse.currentIterator(); + return (ResultIterator<T>) queryResponse.firstIterator(); } - /** * Performs a single database select query returning result as a * ResultIterator. It is caller's responsibility to explicitly close the diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/DataDomainQueryAction.java b/cayenne-server/src/main/java/org/apache/cayenne/access/DataDomainQueryAction.java index d13f06138..f1b01a4ba 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/access/DataDomainQueryAction.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/access/DataDomainQueryAction.java @@ -54,7 +54,6 @@ import org.apache.cayenne.reflect.LifecycleCallbackRegistry; import org.apache.cayenne.tx.BaseTransaction; import org.apache.cayenne.tx.Transaction; import org.apache.cayenne.util.GenericResponse; -import org.apache.cayenne.util.IteratedQueryResponse; import org.apache.cayenne.util.ListResponse; import org.apache.cayenne.util.Util; import org.slf4j.Logger; @@ -97,7 +96,6 @@ class DataDomainQueryAction implements QueryRouter, OperationObserver { private Map<QueryEngine, Collection<Query>> queriesByNode; private Map<Query, Query> queriesByExecutedQueries; private boolean noObjectConversion; - private IteratedQueryResponse iteratedQueryResponse; /* * A constructor for the "new" way of performing a query via 'execute' with @@ -159,7 +157,7 @@ class DataDomainQueryAction implements QueryRouter, OperationObserver { } private void validateIteratedQuery() { - if (metadata.getPageSize() > 0){ + if (metadata.getPageSize() > 0) { throw new CayenneRuntimeException("Pagination is not supported with iterator"); } if (metadata.getPrefetchTree() != null) { @@ -167,7 +165,7 @@ class DataDomainQueryAction implements QueryRouter, OperationObserver { if (prefetchTreeNode.isDisjointPrefetch()) { throw new CayenneRuntimeException("\"Disjoint\" semantic doesn't work with iterator. Use \"Joint\" instead"); } - if (prefetchTreeNode.isDisjointByIdPrefetch()){ + if (prefetchTreeNode.isDisjointByIdPrefetch()) { LOGGER.warn("A separate select query will be created for each iterated item"); } } @@ -224,15 +222,13 @@ class DataDomainQueryAction implements QueryRouter, OperationObserver { DataRow row = null; - if (cache != null && !oidQuery.isFetchMandatory()) { - row = polymorphicRowFromCache(oid); - } + if (cache != null && !oidQuery.isFetchMandatory()) { + row = polymorphicRowFromCache(oid); + } // refresh is forced or not found in cache if (row == null) { - if (oidQuery.isFetchAllowed()) { - runQueryInTransaction(); } else { response = new ListResponse(); @@ -246,38 +242,38 @@ class DataDomainQueryAction implements QueryRouter, OperationObserver { return !DONE; } - - private DataRow polymorphicRowFromCache(ObjectId superOid) { - DataRow row = cache.getCachedSnapshot(superOid); - if (row != null) { - return row; - } - - EntityInheritanceTree inheritanceTree = domain.getEntityResolver().getInheritanceTree(superOid.getEntityName()); - if (!inheritanceTree.getChildren().isEmpty()) { - row = polymorphicRowFromCache(inheritanceTree, superOid); - } - - return row; - } - - private DataRow polymorphicRowFromCache(EntityInheritanceTree superNode, ObjectId superOid) { - - for (EntityInheritanceTree child : superNode.getChildren()) { - ObjectId id = ObjectId.of(child.getEntity().getName(), superOid); - DataRow row = cache.getCachedSnapshot(id); - if (row != null) { - return row; - } - - row = polymorphicRowFromCache(child, superOid); - if (row != null) { - return row; - } - } - - return null; - } + + private DataRow polymorphicRowFromCache(ObjectId superOid) { + DataRow row = cache.getCachedSnapshot(superOid); + if (row != null) { + return row; + } + + EntityInheritanceTree inheritanceTree = domain.getEntityResolver().getInheritanceTree(superOid.getEntityName()); + if (!inheritanceTree.getChildren().isEmpty()) { + row = polymorphicRowFromCache(inheritanceTree, superOid); + } + + return row; + } + + private DataRow polymorphicRowFromCache(EntityInheritanceTree superNode, ObjectId superOid) { + + for (EntityInheritanceTree child : superNode.getChildren()) { + ObjectId id = ObjectId.of(child.getEntity().getName(), superOid); + DataRow row = cache.getCachedSnapshot(id); + if (row != null) { + return row; + } + + row = polymorphicRowFromCache(child, superOid); + if (row != null) { + return row; + } + } + + return null; + } private boolean interceptRelationshipQuery() { @@ -319,7 +315,7 @@ class DataDomainQueryAction implements QueryRouter, OperationObserver { // null id means that FK is null... if (targetId == null) { - this.response = new GenericResponse(Collections.EMPTY_LIST); + this.response = new GenericResponse(Collections.emptyList()); return DONE; } @@ -331,15 +327,11 @@ class DataDomainQueryAction implements QueryRouter, OperationObserver { return DONE; } - // check whether a non-null FK is enough to assume non-null target, - // and if so, - // create a fault + // check whether a non-null FK is enough to assume non-null target, and if so, create a fault if (context != null && relationship.isSourceDefiningTargetPrecenseAndType(domain.getEntityResolver())) { - // prevent passing partial snapshots to ObjectResolver per - // CAY-724. - // Create a hollow object right here and skip object conversion - // downstream + // prevent passing partial snapshots to ObjectResolver per CAY-724. + // Create a hollow object right here and skip object conversion downstream this.noObjectConversion = true; Object object = context.findOrCreateObject(targetId); @@ -354,6 +346,7 @@ class DataDomainQueryAction implements QueryRouter, OperationObserver { /** * @since 3.0 */ + @SuppressWarnings("deprecation") private boolean interceptRefreshQuery() { if (query instanceof RefreshQuery) { @@ -403,8 +396,7 @@ class DataDomainQueryAction implements QueryRouter, OperationObserver { return DONE; } - // 3. refresh query - this shouldn't normally happen as child - // datacontext + // 3. refresh query - this shouldn't normally happen as child datacontext // usually does a cascading refresh if (refreshQuery.getQuery() != null) { Query cachedQuery = refreshQuery.getQuery(); @@ -455,11 +447,10 @@ class DataDomainQueryAction implements QueryRouter, OperationObserver { if (cache) { boolean wasResponseNull = (response == null); - List cachedResults = queryCache.get(metadata, factory); + List<?> cachedResults = queryCache.get(metadata, factory); - // response may already be initialized by the factory above ... it - // is null if - // there was a preexisting cache entry + // response may already be initialized by the factory above ... + // it is null if there was a preexisting cache entry if (response == null || wasResponseNull) { response = new ListResponse(cachedResults); } @@ -468,8 +459,7 @@ class DataDomainQueryAction implements QueryRouter, OperationObserver { this.prefetchResultsByPath = ((ListWithPrefetches) cachedResults).getPrefetchResultsByPath(); } } else { - // on cache-refresh request, fetch without blocking and fill the - // cache + // on cache-refresh request, fetch without blocking and fill the cache queryCache.put(metadata, factory.createObject()); } @@ -530,72 +520,44 @@ class DataDomainQueryAction implements QueryRouter, OperationObserver { } } - private void runIteratedQuery(Transaction tx){ - // reset - this.fullResponse = null; - this.response = null; - this.queriesByNode = null; - this.queriesByExecutedQueries = null; - - // whether this is null or not will driver further decisions on how to process prefetched rows - this.prefetchResultsByPath = metadata.getPrefetchTree() != null && !metadata.isFetchingDataRows() - ? new HashMap<>() : null; - - // categorize queries by node and by "executable" query... - query.route(this, domain.getEntityResolver(), null); - - // run categorized queries - if (queriesByNode != null) { - for (Map.Entry<QueryEngine, Collection<Query>> entry : queriesByNode.entrySet()) { - QueryEngine nextNode = entry.getKey(); - Collection<Query> nodeQueries = entry.getValue(); - nextNode.performQueries(nodeQueries, this); - } + private void runIteratedQuery(Transaction tx) { + runQuery(); + ResultIterator<?> iterator = fullResponse.firstIterator(); + if (iterator == null) { + throw new IllegalStateException("Iterator response expected"); } - - wrapResponseIteratorWithTransactionDecorator(tx); - this.fullResponse = iteratedQueryResponse; - this.response = iteratedQueryResponse; - } - - private void wrapResponseIteratorWithTransactionDecorator(Transaction tx){ - ResultIterator<?> iterator = iteratedQueryResponse.currentIterator(); - TransactionResultIteratorDecorator<?> decorator = new TransactionResultIteratorDecorator<>(iterator, tx); - iteratedQueryResponse.setIterator(decorator); - } - - private void wrapResponseIteratorWithConverterDecorator(ObjectConversionStrategy<?, ?> converter) { - ResultIterator<?> iterator = response.currentIterator(); - ResultIteratorConverterDecorator decorator = new ResultIteratorConverterDecorator(iterator, converter); - iteratedQueryResponse.setIterator(decorator); + fullResponse.replaceResult(iterator, new TransactionResultIteratorDecorator<>(iterator, tx)); + fullResponse.reset(); } - @SuppressWarnings("unchecked") + @SuppressWarnings({"unchecked", "rawtypes"}) private void interceptObjectConversion() { - if (context != null) { - ObjectConversionStrategy<?,?> converter = getConverter(); - ResultIterator<?> iterator = response.firstIterator(); - if (iterator != null) { - wrapResponseIteratorWithConverterDecorator(converter); - } else { - List mainRows = response.firstList(); // List<DataRow> or List<Object[]> + if (context == null) { + return; + } + + ObjectConversionStrategy<?, ?> converter = getConverter(); + // local copy because it can change while iterating + QueryResponse response = this.response; + for (response.reset(); response.next();) { + if (response.isList()) { + List<?> mainRows = response.currentList(); // List<DataRow> or List<Object[]> if (mainRows != null && !mainRows.isEmpty()) { - converter.convert(mainRows); - rewindResponseAfterFirstListCall(); + converter.convert((List) mainRows); } + } else if (response.isIterator()) { + // iterator should be a part of full response + ResultIterator<?> iterator = fullResponse.currentIterator(); + fullResponse.replaceResult(iterator, new ResultIteratorConverterDecorator(iterator, converter)); } } - } - - - private void rewindResponseAfterFirstListCall() { response.reset(); } - private ObjectConversionStrategy<?,?> getConverter() { - ObjectConversionStrategy<?,?> converter; + private ObjectConversionStrategy<?, ?> getConverter() { + ObjectConversionStrategy<?, ?> converter; - if(metadata.isFetchingDataRows()) { + if (metadata.isFetchingDataRows()) { converter = new IdentityConversionStrategy(); } else { List<Object> rsMapping = metadata.getResultSetMapping(); @@ -605,8 +567,8 @@ class DataDomainQueryAction implements QueryRouter, OperationObserver { if (metadata.isSingleResultSetMapping()) { if (rsMapping.get(0) instanceof EntityResultSegment) { converter = new SingleObjectConversionStrategy(); - } else if(rsMapping.get(0) instanceof EmbeddableResultSegment) { - converter = new SingleEmbeddableConversionStrategy(); + } else if (rsMapping.get(0) instanceof EmbeddableResultSegment) { + converter = new SingleEmbeddableConversionStrategy(); } else { converter = new SingleScalarConversionStrategy(); } @@ -616,7 +578,7 @@ class DataDomainQueryAction implements QueryRouter, OperationObserver { } } - if(metadata.getResultMapper() != null) { + if (metadata.getResultMapper() != null) { converter = new MapperConversionStrategy(converter); } return converter; @@ -639,9 +601,7 @@ class DataDomainQueryAction implements QueryRouter, OperationObserver { queries.add(query); - // handle case when routing resulted in an "executable" query different - // from the - // original query. + // handle case when routing resulted in an "executable" query different from the original query. if (substitutedQuery != null && substitutedQuery != query) { if (queriesByExecutedQueries == null) { @@ -702,10 +662,9 @@ class DataDomainQueryAction implements QueryRouter, OperationObserver { @Override public void nextRows(Query query, List<?> dataRows) { - // exclude prefetched rows in the main result if (prefetchResultsByPath != null && query instanceof PrefetchSelectQuery) { - PrefetchSelectQuery prefetchQuery = (PrefetchSelectQuery) query; + PrefetchSelectQuery<?> prefetchQuery = (PrefetchSelectQuery<?>) query; prefetchResultsByPath.put(prefetchQuery.getPrefetchPath(), dataRows); } else { fullResponse.addResultList(dataRows); @@ -714,13 +673,12 @@ class DataDomainQueryAction implements QueryRouter, OperationObserver { @Override public void nextRows(Query q, ResultIterator<?> it) { - iteratedQueryResponse = new IteratedQueryResponse(it); // exclude prefetched rows in the main result if (prefetchResultsByPath != null && query instanceof PrefetchSelectQuery) { - PrefetchSelectQuery prefetchQuery = (PrefetchSelectQuery) query; + PrefetchSelectQuery<?> prefetchQuery = (PrefetchSelectQuery<?>) query; prefetchResultsByPath.put(prefetchQuery.getPrefetchPath(), (List<?>) it); } else { - this.fullResponse = iteratedQueryResponse; + this.fullResponse.addResultIterator(it); } } @@ -748,13 +706,13 @@ class DataDomainQueryAction implements QueryRouter, OperationObserver { return (query instanceof IteratedQueryDecorator); } - abstract class ObjectConversionStrategy<T, R> { + abstract class ObjectConversionStrategy<T, R> { abstract void convert(List<T> mainRows); abstract R convert(T t); protected PrefetchProcessorNode toResultsTree(ClassDescriptor descriptor, PrefetchTreeNode prefetchTree, - List<DataRow> normalizedRows) { + List<DataRow> normalizedRows) { // take a shortcut when no prefetches exist... if (prefetchTree == null) { @@ -767,7 +725,7 @@ class DataDomainQueryAction implements QueryRouter, OperationObserver { } } - protected void updateResponse(List sourceObjects, List targetObjects) { + protected void updateResponse(List<T> sourceObjects, List<? extends R> targetObjects) { if (response instanceof GenericResponse) { ((GenericResponse) response).replaceResult(sourceObjects, targetObjects); } else if (response instanceof ListResponse) { @@ -810,7 +768,7 @@ class DataDomainQueryAction implements QueryRouter, OperationObserver { } @Override - Object convert(DataRow dataRow) { + Object convert(DataRow dataRow) { PrefetchProcessorNode node = getPrefetchProcessorNode(Collections.singletonList(dataRow)); return node.getObjects().get(0); } @@ -820,8 +778,8 @@ class DataDomainQueryAction implements QueryRouter, OperationObserver { List<Object> rsMapping = metadata.getResultSetMapping(); EntityResultSegment resultSegment = null; - if(rsMapping != null && !rsMapping.isEmpty()) { - resultSegment = (EntityResultSegment)rsMapping.get(0); + if (rsMapping != null && !rsMapping.isEmpty()) { + resultSegment = (EntityResultSegment) rsMapping.get(0); } ClassDescriptor descriptor = resultSegment == null @@ -850,14 +808,14 @@ class DataDomainQueryAction implements QueryRouter, OperationObserver { @Override void convert(List<DataRow> mainRows) { - EmbeddableResultSegment resultSegment = (EmbeddableResultSegment)metadata.getResultSetMapping().get(0); + EmbeddableResultSegment resultSegment = (EmbeddableResultSegment) metadata.getResultSetMapping().get(0); Embeddable embeddable = resultSegment.getEmbeddable(); Class<?> embeddableClass = objectFactory.getJavaClass(embeddable.getClassName()); List<EmbeddableObject> result = new ArrayList<>(mainRows.size()); mainRows.forEach(dataRow -> { EmbeddableObject eo; try { - eo = (EmbeddableObject)embeddableClass.getDeclaredConstructor().newInstance(); + eo = (EmbeddableObject) embeddableClass.getDeclaredConstructor().newInstance(); } catch (Exception e) { throw new CayenneRuntimeException("Unable to materialize embeddable '%s'", e, embeddable.getClassName()); } @@ -877,7 +835,7 @@ class DataDomainQueryAction implements QueryRouter, OperationObserver { class MixedConversionStrategy extends ObjectConversionStrategy<Object[], Object[]> { protected PrefetchProcessorNode toResultsTree(ClassDescriptor descriptor, PrefetchTreeNode prefetchTree, - List<Object[]> rows, int position) { + List<Object[]> rows, int position) { List<DataRow> rowsColumn = new ArrayList<>(rows.size()); for (Object[] row : rows) { @@ -936,13 +894,13 @@ class DataDomainQueryAction implements QueryRouter, OperationObserver { row[i] = objects.get(j); } } else if (mapping instanceof EmbeddableResultSegment) { - EmbeddableResultSegment resultSegment = (EmbeddableResultSegment)mapping; + EmbeddableResultSegment resultSegment = (EmbeddableResultSegment) mapping; Embeddable embeddable = resultSegment.getEmbeddable(); Class<?> embeddableClass = objectFactory.getJavaClass(embeddable.getClassName()); try { - for(Object[] row : mainRows) { - DataRow dataRow = (DataRow)row[i]; - EmbeddableObject eo = (EmbeddableObject)embeddableClass.getDeclaredConstructor().newInstance(); + for (Object[] row : mainRows) { + DataRow dataRow = (DataRow) row[i]; + EmbeddableObject eo = (EmbeddableObject) embeddableClass.getDeclaredConstructor().newInstance(); dataRow.forEach(eo::writePropertyDirectly); row[i] = eo; } @@ -952,7 +910,7 @@ class DataDomainQueryAction implements QueryRouter, OperationObserver { } } - if(!metadata.isSuppressingDistinct()) { + if (!metadata.isSuppressingDistinct()) { Set<List<?>> seen = new HashSet<>(mainRows.size()); mainRows.removeIf(objects -> !seen.add(Arrays.asList(objects))); } @@ -998,8 +956,8 @@ class DataDomainQueryAction implements QueryRouter, OperationObserver { @SuppressWarnings({"unchecked", "rawtypes"}) MapperConversionStrategy(ObjectConversionStrategy<?, ?> parentStrategy) { - this.mapper = (Function)metadata.getResultMapper(); - this.parentStrategy = (ObjectConversionStrategy)parentStrategy; + this.mapper = (Function) metadata.getResultMapper(); + this.parentStrategy = (ObjectConversionStrategy) parentStrategy; } @Override diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/ResultIteratorConverterDecorator.java b/cayenne-server/src/main/java/org/apache/cayenne/access/ResultIteratorConverterDecorator.java index 9c26e62ee..08fb54380 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/access/ResultIteratorConverterDecorator.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/access/ResultIteratorConverterDecorator.java @@ -25,18 +25,21 @@ import java.util.Iterator; import java.util.List; import java.util.Objects; - class ResultIteratorConverterDecorator implements ResultIterator { - private final ResultIterator iterator; - private final DataDomainQueryAction.ObjectConversionStrategy converter; +class ResultIteratorConverterDecorator<T, R> implements ResultIterator<R> { + private final ResultIterator<T> iterator; + private final DataDomainQueryAction.ObjectConversionStrategy<T, R> converter; - ResultIteratorConverterDecorator(ResultIterator iterator, DataDomainQueryAction.ObjectConversionStrategy converter) { + ResultIteratorConverterDecorator(ResultIterator<T> iterator, DataDomainQueryAction.ObjectConversionStrategy<T, R> converter) { this.iterator = Objects.requireNonNull(iterator); this.converter = Objects.requireNonNull(converter); } + @SuppressWarnings("unchecked") @Override - public List allRows() { - return iterator.allRows(); + public List<R> allRows() { + List<T> mainRows = iterator.allRows(); + converter.convert(mainRows); + return (List<R>) mainRows; } @Override @@ -45,7 +48,7 @@ import java.util.Objects; } @Override - public Object nextRow() { + public R nextRow() { return converter.convert(iterator.nextRow()); } @@ -60,17 +63,17 @@ import java.util.Objects; } @Override - public Iterator iterator() { - return new Iterator() { + public Iterator<R> iterator() { + return new Iterator<>() { @Override public boolean hasNext() { return iterator.hasNextRow(); } @Override - public Object next() { + public R next() { return converter.convert(iterator.nextRow()); } }; } - } +} diff --git a/cayenne-server/src/main/java/org/apache/cayenne/util/IteratedQueryResponse.java b/cayenne-server/src/main/java/org/apache/cayenne/util/IteratedQueryResponse.java deleted file mode 100644 index 72e9913b2..000000000 --- a/cayenne-server/src/main/java/org/apache/cayenne/util/IteratedQueryResponse.java +++ /dev/null @@ -1,65 +0,0 @@ -/***************************************************************** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - ****************************************************************/ - -package org.apache.cayenne.util; - -import org.apache.cayenne.ResultIterator; - -/** - * Implementation of QueryResponse for iterated query. - * - * @since 5.0 - */ -public class IteratedQueryResponse extends GenericResponse { - private ResultIterator<?> iterator; - - public IteratedQueryResponse(ResultIterator<?> iterator) { - this.iterator = iterator; - } - - public void setIterator(ResultIterator<?> iterator) { - this.iterator = iterator; - } - - @Override - public int size() { - return -1; - } - - @Override - public boolean isIterator() { - return true; - } - - @Override - public ResultIterator<?> currentIterator() { - return iterator; - } - - @Override - public boolean next() { - return false; - } - - @Override - public ResultIterator<?> firstIterator() { - return iterator; - } - -} diff --git a/cayenne-server/src/test/java/org/apache/cayenne/util/GenericResponseTest.java b/cayenne-server/src/test/java/org/apache/cayenne/util/GenericResponseTest.java index 6184d4b14..f55d0b846 100644 --- a/cayenne-server/src/test/java/org/apache/cayenne/util/GenericResponseTest.java +++ b/cayenne-server/src/test/java/org/apache/cayenne/util/GenericResponseTest.java @@ -33,7 +33,7 @@ import static org.junit.Assert.assertTrue; public class GenericResponseTest { @Test - public void testCreation() throws Exception { + public void testCreation() { List list = new ArrayList(); list.add(new HashMap()); @@ -58,6 +58,18 @@ public class GenericResponseTest { assertFalse(r.next()); } + @Test + public void testNext() { + List<Integer> result = List.of(1, 2, 3); + GenericResponse r = new GenericResponse(); + r.addResultList(result); + + assertTrue(r.next()); + assertTrue(r.isList()); + assertEquals(result, r.currentList()); + assertFalse(r.next()); + } + @Test public void testSerialization() throws Exception { List<Object> list = new ArrayList<>(); diff --git a/cayenne-server/src/test/java/org/apache/cayenne/util/ListResponseTest.java b/cayenne-server/src/test/java/org/apache/cayenne/util/ListResponseTest.java index 60cf91a57..3fe1901a2 100644 --- a/cayenne-server/src/test/java/org/apache/cayenne/util/ListResponseTest.java +++ b/cayenne-server/src/test/java/org/apache/cayenne/util/ListResponseTest.java @@ -32,7 +32,7 @@ import static org.junit.Assert.assertTrue; public class ListResponseTest { @Test - public void testCreation() throws Exception { + public void testCreation() { Object object = new Object(); ListResponse r = new ListResponse(object); @@ -54,12 +54,23 @@ public class ListResponseTest { assertSame(currentList, r.firstList()); } + @Test + public void testNext() { + List<Integer> result = List.of(1, 2, 3); + ListResponse r = new ListResponse(result); + + assertTrue(r.next()); + assertTrue(r.isList()); + assertEquals(result, r.currentList()); + assertFalse(r.next()); + } + @Test public void testSerialization() throws Exception { ListResponse r = new ListResponse(67); - ListResponse sr = (ListResponse) Util.cloneViaSerialization(r); + ListResponse sr = Util.cloneViaSerialization(r); assertNotNull(sr); assertEquals(1, sr.size());