From 322e9771b5eba8d1ad216466d90f28f555d53612 Mon Sep 17 00:00:00 2001 From: Ivan Nikitka <70625960+Ivan-nikitko@users.noreply.github.com> Date: Wed, 27 Sep 2023 12:16:53 +0200 Subject: [PATCH 01/13] new IteratedQuery processing --- .../org/apache/cayenne/QueryResponse.java | 5 + .../apache/cayenne/access/DataContext.java | 24 +- .../cayenne/access/DataDomainQueryAction.java | 254 ++++++++++++++---- .../ResultIteratorConverterDecorator.java | 94 +++++++ .../cayenne/query/IteratedQueryDecorator.java | 50 ++++ .../apache/cayenne/util/GenericResponse.java | 5 + .../cayenne/util/IteratedQueryResponse.java | 79 ++++++ .../org/apache/cayenne/util/ListResponse.java | 5 + .../apache/cayenne/access/EmbeddingIT.java | 11 + .../apache/cayenne/query/IteratedQueryIT.java | 184 +++++++++++++ 10 files changed, 639 insertions(+), 72 deletions(-) create mode 100644 cayenne-server/src/main/java/org/apache/cayenne/access/ResultIteratorConverterDecorator.java create mode 100644 cayenne-server/src/main/java/org/apache/cayenne/query/IteratedQueryDecorator.java create mode 100644 cayenne-server/src/main/java/org/apache/cayenne/util/IteratedQueryResponse.java create mode 100644 cayenne-server/src/test/java/org/apache/cayenne/query/IteratedQueryIT.java diff --git a/cayenne-server/src/main/java/org/apache/cayenne/QueryResponse.java b/cayenne-server/src/main/java/org/apache/cayenne/QueryResponse.java index c7a481d90c..d301d4956f 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/QueryResponse.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/QueryResponse.java @@ -68,6 +68,11 @@ public interface QueryResponse { */ boolean isList(); + /** + * Returns whether current response is an iterator + */ + boolean isIterator(); + /** * Returns a List under the current iterator position. Use {@link #isList()} to check * the result type before calling this method. 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 e6d91cfa6e..998e0cc887 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 @@ -65,6 +65,7 @@ import org.apache.cayenne.tx.TransactionFactory; import org.apache.cayenne.util.EventUtil; import org.apache.cayenne.util.GenericResponse; +import org.apache.cayenne.util.IteratedQueryResponse; import org.apache.cayenne.util.ResultIteratorIterator; import org.apache.cayenne.util.Util; @@ -796,18 +797,8 @@ GraphDiff flushToParent(boolean cascade) { @SuppressWarnings("unchecked") @Override public ResultIterator iterator(final Select query) { - final ResultIterator rows = performIteratedQuery(query); - final QueryMetadata md = query.getMetaData(getEntityResolver()); - - if (md.isFetchingDataRows() || isObjectArrayResult(md)) { - // no need to convert result - return (ResultIterator) rows; - } else { - // this is a bit optimized version of 'objectFromDataRow' with - // resolver cached for reuse... still the rest is pretty suboptimal - final ObjectResolver resolver = new ObjectResolver(this, md.getClassDescriptor(), true); - return new DataRowResultIterator(rows, resolver); - } + IteratedQueryDecorator queryDecorator = new IteratedQueryDecorator(query); + return (ResultIterator) performQuery(queryDecorator).get(0); } /** @@ -937,11 +928,16 @@ public QueryResponse performGenericQuery(Query query) { * * @return A list of DataObjects or a DataRows, depending on the value * returned by {@link QueryMetadata#isFetchingDataRows()}. + * Сan also return an iterator if the query is an instance of iteratedQuery. */ @Override @SuppressWarnings("unchecked") public List performQuery(Query query) { query = nonNullDelegate().willPerformQuery(this, query); + if (query instanceof IteratedQueryDecorator){ + IteratedQueryResponse queryResponse = (IteratedQueryResponse) (onQuery(this, query)); + return Collections.singletonList(queryResponse.getIterator()); + } if (query == null) { return new ArrayList<>(1); } @@ -1028,7 +1024,7 @@ public List performQuery(String queryName, boolean expireCachedLists) { * @since 1.1 */ public List performQuery(String queryName, Map parameters, boolean expireCachedLists) { - return performQuery(expireCachedLists ? + return (List) performQuery(expireCachedLists ? MappedSelect.query(queryName).params(parameters).forceNoCache() : MappedSelect.query(queryName).params(parameters)); } @@ -1176,7 +1172,7 @@ protected void fireDataChannelChanged(Object postedBy, GraphDiff changes) { super.fireDataChannelChanged(postedBy, changes); } - private TransactionFactory getTransactionFactory() { + TransactionFactory getTransactionFactory() { attachToRuntimeIfNeeded(); return transactionFactory; } 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 5ec153e55a..0522dbff73 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 @@ -39,6 +39,7 @@ import org.apache.cayenne.map.ObjRelationship; import org.apache.cayenne.query.EmbeddableResultSegment; import org.apache.cayenne.query.EntityResultSegment; +import org.apache.cayenne.query.IteratedQueryDecorator; import org.apache.cayenne.query.ObjectIdQuery; import org.apache.cayenne.query.PrefetchSelectQuery; import org.apache.cayenne.query.PrefetchTreeNode; @@ -50,7 +51,10 @@ import org.apache.cayenne.query.RelationshipQuery; import org.apache.cayenne.reflect.ClassDescriptor; 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; @@ -72,7 +76,7 @@ * * @since 1.2 */ -class DataDomainQueryAction implements QueryRouter, OperationObserver { +public class DataDomainQueryAction implements QueryRouter, OperationObserver { static final boolean DONE = true; @@ -89,6 +93,7 @@ class DataDomainQueryAction implements QueryRouter, OperationObserver { Map> queriesByNode; Map queriesByExecutedQueries; boolean noObjectConversion; + private IteratedQueryResponse iteratedQueryResponse; /* * A constructor for the "new" way of performing a query via 'execute' with @@ -119,12 +124,14 @@ class DataDomainQueryAction implements QueryRouter, OperationObserver { QueryResponse execute() { // run chain... - if (interceptOIDQuery() != DONE) { - if (interceptRelationshipQuery() != DONE) { - if (interceptRefreshQuery() != DONE) { - if (interceptSharedCache() != DONE) { - if (interceptDataDomainQuery() != DONE) { - runQueryInTransaction(); + if (interceptIteratedQuery() != DONE) { + if (interceptOIDQuery() != DONE) { + if (interceptRelationshipQuery() != DONE) { + if (interceptRefreshQuery() != DONE) { + if (interceptSharedCache() != DONE) { + if (interceptDataDomainQuery() != DONE) { + runQueryInTransaction(); + } } } } @@ -138,6 +145,37 @@ QueryResponse execute() { return response; } + private boolean interceptIteratedQuery() { + if (query instanceof IteratedQueryDecorator) { + performIteratedQuery(); + return DONE; + } + return !DONE; + } + + private void performIteratedQuery() { + Transaction tx = BaseTransaction.getThreadTransaction(); + if (tx != null) { + runIteratedQuery(tx); + } else { + tx = context.getTransactionFactory().createTransaction(); + BaseTransaction.bindThreadTransaction(tx); + try { + runIteratedQuery(tx); + } catch (Exception e) { + throw new CayenneRuntimeException(e); + } finally { + BaseTransaction.bindThreadTransaction(null); + if (tx.isRollbackOnly()) { + try { + tx.rollback(); + } catch (Exception rollbackEx) { + } + } + } + } + } + private boolean interceptDataDomainQuery() { if (query instanceof DataDomainQuery) { response = new ListResponse(domain); @@ -471,45 +509,95 @@ private void runQuery() { } } + 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> entry : queriesByNode.entrySet()) { + QueryEngine nextNode = entry.getKey(); + Collection nodeQueries = entry.getValue(); + nextNode.performQueries(nodeQueries, this); + } + } + + wrapResponseIteratorWithTransactionDecorator(tx); + this.fullResponse = iteratedQueryResponse; + this.response = iteratedQueryResponse; + } + + private void wrapResponseIteratorWithTransactionDecorator(Transaction tx){ + ResultIterator iterator = iteratedQueryResponse.getIterator(); + TransactionResultIteratorDecorator decorator = new TransactionResultIteratorDecorator<>(iterator, tx); + iteratedQueryResponse.setIterator(decorator); + } + + private void wrapResponseIteratorWithConverterDecorator(ObjectConversionStrategy converter) { + ResultIterator iterator = ((IteratedQueryResponse) response).getIterator(); + ResultIteratorConverterDecorator decorator = new ResultIteratorConverterDecorator(iterator, converter); + ((IteratedQueryResponse) response).setIterator(decorator); + } + @SuppressWarnings("unchecked") private void interceptObjectConversion() { - if (context != null) { - List mainRows = response.firstList(); // List or List - if (mainRows != null && !mainRows.isEmpty()) { + ObjectConversionStrategy converter = getConverter(); + if (response.isIterator()) { + wrapResponseIteratorWithConverterDecorator(converter); + } else { + List mainRows = response.firstList(); // List or List + if (mainRows != null && !mainRows.isEmpty()) { + converter.convert(mainRows); + rewindResponseAfterFirstListCall(); + } + } + } + } - ObjectConversionStrategy converter; - if(metadata.isFetchingDataRows()) { - converter = new IdentityConversionStrategy(); - } else { - List rsMapping = metadata.getResultSetMapping(); - if (rsMapping == null) { + private void rewindResponseAfterFirstListCall() { + response.reset(); + } + + private ObjectConversionStrategy getConverter() { + ObjectConversionStrategy converter; + + if(metadata.isFetchingDataRows()) { + converter = new IdentityConversionStrategy(); + } else { + List rsMapping = metadata.getResultSetMapping(); + if (rsMapping == null) { + converter = new SingleObjectConversionStrategy(); + } else { + if (metadata.isSingleResultSetMapping()) { + if (rsMapping.get(0) instanceof EntityResultSegment) { converter = new SingleObjectConversionStrategy(); + } else if(rsMapping.get(0) instanceof EmbeddableResultSegment) { + converter = new SingleEmbeddableConversionStrategy(); } else { - if (metadata.isSingleResultSetMapping()) { - if (rsMapping.get(0) instanceof EntityResultSegment) { - converter = new SingleObjectConversionStrategy(); - } else if(rsMapping.get(0) instanceof EmbeddableResultSegment) { - converter = new SingleEmbeddableConversionStrategy(); - } else { - converter = new SingleScalarConversionStrategy(); - } - } else { - converter = new MixedConversionStrategy(); - } + converter = new SingleScalarConversionStrategy(); } + } else { + converter = new MixedConversionStrategy(); } - - if(metadata.getResultMapper() != null) { - converter = new MapperConversionStrategy(converter); - } - - converter.convert(mainRows); - // rewind response after firstList() call - response.reset(); } } + + if(metadata.getResultMapper() != null) { + converter = new MapperConversionStrategy(converter); + } + return converter; } @Override @@ -604,7 +692,14 @@ public void nextRows(Query query, List dataRows) { @Override public void nextRows(Query q, ResultIterator it) { - throw new CayenneRuntimeException("Invalid attempt to fetch a cursor."); + iteratedQueryResponse = new IteratedQueryResponse(it); + // exclude prefetched rows in the main result + if (prefetchResultsByPath != null && query instanceof PrefetchSelectQuery) { + PrefetchSelectQuery prefetchQuery = (PrefetchSelectQuery) query; + prefetchResultsByPath.put(prefetchQuery.getPrefetchPath(), (List) it); + } else { + this.fullResponse = iteratedQueryResponse; + } } @Override @@ -630,13 +725,14 @@ public void nextGlobalException(Exception e) { @Override public boolean isIteratedResult() { - return false; + return (query instanceof IteratedQueryDecorator); } - abstract class ObjectConversionStrategy { - + abstract class ObjectConversionStrategy { abstract void convert(List mainRows); + abstract R convert(T t); + protected PrefetchProcessorNode toResultsTree(ClassDescriptor descriptor, PrefetchTreeNode prefetchTree, List normalizedRows) { @@ -676,11 +772,30 @@ protected void performPostLoadCallbacks(PrefetchProcessorNode node, LifecycleCal } } - class SingleObjectConversionStrategy extends ObjectConversionStrategy { + class SingleObjectConversionStrategy extends ObjectConversionStrategy { @Override - void convert(List mainRows) { + public void convert(List mainRows) { + + PrefetchProcessorNode node = getPrefetchProcessorNode(mainRows); + List objects = node.getObjects(); + updateResponse(mainRows, objects != null ? objects : new ArrayList<>(1)); + + // apply POST_LOAD callback + LifecycleCallbackRegistry callbackRegistry = context.getEntityResolver().getCallbackRegistry(); + if (!callbackRegistry.isEmpty(LifecycleEvent.POST_LOAD)) { + performPostLoadCallbacks(node, callbackRegistry); + } + } + + @Override + Object convert(DataRow dataRow) { + PrefetchProcessorNode node = getPrefetchProcessorNode(Collections.singletonList(dataRow)); + return node.getObjects().get(0); + } + + private PrefetchProcessorNode getPrefetchProcessorNode(List mainRows) { PrefetchTreeNode prefetchTree = metadata.getPrefetchTree(); List rsMapping = metadata.getResultSetMapping(); @@ -694,27 +809,25 @@ void convert(List mainRows) { : resultSegment.getClassDescriptor(); PrefetchProcessorNode node = toResultsTree(descriptor, prefetchTree, mainRows); - List objects = node.getObjects(); - updateResponse(mainRows, objects != null ? objects : new ArrayList<>(1)); - - // apply POST_LOAD callback - LifecycleCallbackRegistry callbackRegistry = context.getEntityResolver().getCallbackRegistry(); - - if (!callbackRegistry.isEmpty(LifecycleEvent.POST_LOAD)) { - performPostLoadCallbacks(node, callbackRegistry); - } + return node; } } - class SingleScalarConversionStrategy extends ObjectConversionStrategy { + class SingleScalarConversionStrategy extends ObjectConversionStrategy { @Override void convert(List mainRows) { // noop... scalars require no further processing } + + @Override + Object convert(Object o) { + return o; + // noop... scalars require no further processing + } } - class SingleEmbeddableConversionStrategy extends ObjectConversionStrategy { + class SingleEmbeddableConversionStrategy extends ObjectConversionStrategy { @Override void convert(List mainRows) { @@ -734,9 +847,15 @@ void convert(List mainRows) { }); updateResponse(mainRows, result); } + + @Override + Object convert(DataRow dataRow) { + convert(Collections.singletonList(dataRow)); + return dataRow; + } } - class MixedConversionStrategy extends ObjectConversionStrategy { + class MixedConversionStrategy extends ObjectConversionStrategy { protected PrefetchProcessorNode toResultsTree(ClassDescriptor descriptor, PrefetchTreeNode prefetchTree, List rows, int position) { @@ -828,24 +947,38 @@ void convert(List mainRows) { } } } + + @Override + Object[] convert(Object[] objectsArray) { + List objects = new ArrayList<>(); + objects.add(objectsArray); + convert(objects); + return objectsArray; + } } - private class IdentityConversionStrategy extends ObjectConversionStrategy { + private class IdentityConversionStrategy extends ObjectConversionStrategy { @Override - void convert(List mainRows) { + void convert(List mainRows) { + //noop + } + + @Override + public Object convert(Object object) { + return object; } } /** * Conversion strategy that uses mapper function to map raw result */ - private class MapperConversionStrategy extends ObjectConversionStrategy { + private class MapperConversionStrategy extends ObjectConversionStrategy { private final Function mapper; - private final ObjectConversionStrategy parentStrategy; + private final ObjectConversionStrategy parentStrategy; @SuppressWarnings({"unchecked", "rawtypes"}) - MapperConversionStrategy(ObjectConversionStrategy parentStrategy) { + MapperConversionStrategy(ObjectConversionStrategy parentStrategy) { this.mapper = (Function)metadata.getResultMapper(); this.parentStrategy = (ObjectConversionStrategy)parentStrategy; } @@ -855,6 +988,11 @@ void convert(List mainRows) { parentStrategy.convert(mainRows); mainRows.replaceAll(mapper::apply); } + + @Override + Object convert(Object object) { + return mapper.apply(parentStrategy.convert(object)); + } } } 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 new file mode 100644 index 0000000000..3533df463d --- /dev/null +++ b/cayenne-server/src/main/java/org/apache/cayenne/access/ResultIteratorConverterDecorator.java @@ -0,0 +1,94 @@ +/***************************************************************** + * 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.access; + +import org.apache.cayenne.ResultIterator; + +import java.util.Iterator; +import java.util.List; + +public class ResultIteratorConverterDecorator implements ResultIterator { + private final ResultIterator iterator; + private final DataDomainQueryAction.ObjectConversionStrategy converter; + + public ResultIteratorConverterDecorator(ResultIterator iterator, DataDomainQueryAction.ObjectConversionStrategy converter) { + this.iterator = iterator; + this.converter = converter; + } + + @Override + public List allRows() { + return iterator.allRows(); + } + + @Override + public boolean hasNextRow() { + return iterator.hasNextRow(); + } + + @Override + public Object nextRow() { + if (converter != null) { + return converter.convert(iterator.nextRow()); + } + return iterator.nextRow(); + } + + @Override + public void skipRow() { + iterator.skipRow(); + } + + @Override + public void close() { + iterator.close(); + } + + @Override + public Iterator iterator() { + return new IteratorConverterDecorator(iterator,converter); + } + +} + +class IteratorConverterDecorator implements Iterator{ + private final ResultIterator iterator; + private final DataDomainQueryAction.ObjectConversionStrategy converter; + + public IteratorConverterDecorator(ResultIterator iterator, DataDomainQueryAction.ObjectConversionStrategy converter) { + this.iterator = iterator; + this.converter = converter; + } + + @Override + public boolean hasNext() { + return iterator.hasNextRow(); + } + + @Override + public Object next() { + if (converter != null) { + return converter.convert(iterator.nextRow()); + } + return iterator.nextRow(); + } + + +} diff --git a/cayenne-server/src/main/java/org/apache/cayenne/query/IteratedQueryDecorator.java b/cayenne-server/src/main/java/org/apache/cayenne/query/IteratedQueryDecorator.java new file mode 100644 index 0000000000..9093bd7f93 --- /dev/null +++ b/cayenne-server/src/main/java/org/apache/cayenne/query/IteratedQueryDecorator.java @@ -0,0 +1,50 @@ +/***************************************************************** + * 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.query; + +import org.apache.cayenne.map.EntityResolver; + +public class IteratedQueryDecorator implements Query{ + + private final Query query; + + public IteratedQueryDecorator(Query query) { + this.query = query; + } + + @Override + public QueryMetadata getMetaData(EntityResolver resolver) { + return query.getMetaData(resolver); + } + + @Override + public void route(QueryRouter router, EntityResolver resolver, Query substitutedQuery) { + query.route(router,resolver,substitutedQuery); + } + + @Override + public SQLAction createSQLAction(SQLActionVisitor visitor) { + return query.createSQLAction(visitor); + } + + public Query getQuery() { + return query; + } +} diff --git a/cayenne-server/src/main/java/org/apache/cayenne/util/GenericResponse.java b/cayenne-server/src/main/java/org/apache/cayenne/util/GenericResponse.java index 01d352d939..1f76968f23 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/util/GenericResponse.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/util/GenericResponse.java @@ -101,6 +101,11 @@ public boolean isList() { return results.get(currentIndex - 1) instanceof List; } + @Override + public boolean isIterator() { + return false; + } + public boolean next() { return ++currentIndex <= results.size(); } 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 new file mode 100644 index 0000000000..c51e75ed03 --- /dev/null +++ b/cayenne-server/src/main/java/org/apache/cayenne/util/IteratedQueryResponse.java @@ -0,0 +1,79 @@ +/***************************************************************** + * 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; + +import java.util.List; + +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; + } + + public ResultIterator getIterator() { + return iterator; + } + + @Override + public boolean isList() { + return false; + } + + @Override + public boolean isIterator() { + return true; + } + + @Override + public List currentList() { + return null; + } + + @Override + public int[] currentUpdateCount() { + return new int[0]; + } + + @Override + public boolean next() { + return false; + } + + @Override + public List firstList() { + return null; + } + + @Override + public int[] firstUpdateCount() { + return new int[0]; + } +} diff --git a/cayenne-server/src/main/java/org/apache/cayenne/util/ListResponse.java b/cayenne-server/src/main/java/org/apache/cayenne/util/ListResponse.java index 9944ab82fe..cb66795230 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/util/ListResponse.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/util/ListResponse.java @@ -64,6 +64,11 @@ public boolean isList() { return true; } + @Override + public boolean isIterator() { + return false; + } + public List currentList() { if (currentIndex != 1) { throw new IndexOutOfBoundsException("Past iteration end: " + currentIndex); diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/EmbeddingIT.java b/cayenne-server/src/test/java/org/apache/cayenne/access/EmbeddingIT.java index c01f36380d..e02f218f71 100644 --- a/cayenne-server/src/test/java/org/apache/cayenne/access/EmbeddingIT.java +++ b/cayenne-server/src/test/java/org/apache/cayenne/access/EmbeddingIT.java @@ -21,6 +21,7 @@ import org.apache.cayenne.DataRow; import org.apache.cayenne.ObjectContext; import org.apache.cayenne.PersistenceState; +import org.apache.cayenne.ResultBatchIterator; import org.apache.cayenne.di.Inject; import org.apache.cayenne.query.ObjectSelect; import org.apache.cayenne.test.jdbc.DBHelper; @@ -360,4 +361,14 @@ public void testInsertWithInheritance() { context.commitChanges(); } + + @Test + public void testQueryWithBatchIterator() throws Exception { + createSelectDataSet2(); + try (ResultBatchIterator iterator = ObjectSelect.query(EmbedEntity1.class) + .batchIterator(context, 2)) { + assertNotNull(iterator.next().get(0).getEmbedded2()); + } + + } } diff --git a/cayenne-server/src/test/java/org/apache/cayenne/query/IteratedQueryIT.java b/cayenne-server/src/test/java/org/apache/cayenne/query/IteratedQueryIT.java new file mode 100644 index 0000000000..636e591c77 --- /dev/null +++ b/cayenne-server/src/test/java/org/apache/cayenne/query/IteratedQueryIT.java @@ -0,0 +1,184 @@ +/***************************************************************** + * 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.query; + +import org.apache.cayenne.ResultBatchIterator; +import org.apache.cayenne.ResultIterator; +import org.apache.cayenne.access.DataContext; +import org.apache.cayenne.di.Inject; +import org.apache.cayenne.test.jdbc.DBHelper; +import org.apache.cayenne.test.jdbc.TableHelper; +import org.apache.cayenne.testdo.testmap.Painting; +import org.apache.cayenne.unit.di.server.CayenneProjects; +import org.apache.cayenne.unit.di.server.ServerCase; +import org.apache.cayenne.unit.di.server.UseServerRuntime; +import org.junit.Before; +import org.junit.Test; + +import java.sql.SQLException; +import java.sql.Types; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + + +@UseServerRuntime(CayenneProjects.TESTMAP_PROJECT) +public class IteratedQueryIT extends ServerCase { + + @Inject + private DataContext context; + + @Inject + private DBHelper dbHelper; + + private TableHelper tPainting; + + private TableHelper tArtist; + + + @Before + public void before() throws Exception { + tPainting = new TableHelper(dbHelper, "PAINTING") + .setColumns("PAINTING_ID", "PAINTING_TITLE", "ESTIMATED_PRICE", "ARTIST_ID") + .setColumnTypes(Types.INTEGER, Types.VARCHAR, Types.DECIMAL, Types.INTEGER); + + tArtist = new TableHelper(dbHelper, "ARTIST") + .setColumns("ARTIST_ID", "ARTIST_NAME"); + + createArtistDataSet(); + createPaintingsDataSet(); + + } + + private void createPaintingsDataSet() throws Exception { + for (int i = 1; i <= 20; i++) { + tPainting.insert(i, "painting" + i, 10000. * i, 1); + } + } + + private void createArtistDataSet() throws SQLException { + tArtist.insert(1, "Test1"); + tArtist.insert(2, "Test2"); + } + + + //SingleObjectConversation + @Test + public void test_prefetchWithBatchIterator() { + Painting painting; + try (ResultBatchIterator iterator = ObjectSelect.query(Painting.class) + .prefetch(Painting.TO_ARTIST.joint()) + .batchIterator(context, 5)) { + painting = iterator.next().get(0); + } + assertTrue(painting instanceof Painting); + } + + @Test + public void test_prefetchWithIterator() { + Painting painting; + try (ResultIterator iterator = ObjectSelect.query(Painting.class) + .prefetch(Painting.TO_ARTIST.joint()) + .iterator(context)) { + painting = iterator.nextRow(); + } + assertTrue(painting instanceof Painting); + } + + //SingleScalarConversationStrategy + @Test + public void test_ScalarQueryWithBatchIterator() { + int id; + try (ResultBatchIterator iterator = SQLSelect.scalarQuery("SELECT PAINTING_ID FROM PAINTING ORDER BY PAINTING_ID", + Integer.class).batchIterator(context, 2)) { + id = iterator.next().get(0); + } + assertEquals(1, id); + } + + //SingleScalarConversationStrategy + @Test + public void test_ScalarQueryWithIterator() { + int id; + try (ResultIterator iterator = SQLSelect.scalarQuery("SELECT PAINTING_ID FROM PAINTING ORDER BY PAINTING_ID", + Integer.class).iterator(context)) { + id = iterator.nextRow(); + } + assertEquals(1, id); + } + + //MapperConversationStrategy + //MixedConversationStrategy + @Test + public void test_MappingWithBatchIterator() { + try (ResultBatchIterator batchIterator = ObjectSelect.columnQuery(Painting.class, Painting.PAINTING_TITLE, Painting.ESTIMATED_PRICE) + .map(this::toDto) + .batchIterator(context, 2)) { + + assertTrue(batchIterator.iterator().next().get(0) instanceof DTO); + } + context.commitChanges(); + } + + //MapperConversationStrategy + //MixedConversationStrategy + @Test + public void test_MappingWithIterator() { + try (ResultIterator iterator = ObjectSelect.columnQuery(Painting.class, Painting.PAINTING_TITLE, Painting.ESTIMATED_PRICE) + .map(this::toDto) + .iterator(context)) { + + assertTrue(iterator.nextRow() instanceof DTO); + } + context.commitChanges(); + } + + + //SingleObjectConversation + @Test + public void UnclosedTransactionsTest() { + for (int i = 0; i < 5; i++) { + test_MappingWithBatchIterator(); + } + } + + + DTO toDto(Object[] data) { + return new DTO(data); + } + + + static class DTO { + private final String title; + private final Long estimatedPrice; + + public DTO(Object[] data) { + this.title = (String) data[0]; + this.estimatedPrice = ((Number) data[1]).longValue(); + } + + @Override + public String toString() { + return "DTO{" + + "title='" + title + '\'' + + ", estimatedPrice=" + estimatedPrice + + '}'; + } + } +} From 9e4699f86bee29002ac1871d7b8108f35340366c Mon Sep 17 00:00:00 2001 From: Ivan Nikitka <70625960+Ivan-nikitko@users.noreply.github.com> Date: Fri, 6 Oct 2023 11:19:24 +0200 Subject: [PATCH 02/13] changed access modifier in DataDomainQueryAction --- .../cayenne/access/DataDomainQueryAction.java | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) 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 0522dbff73..2b9a8a3026 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 @@ -76,23 +76,23 @@ * * @since 1.2 */ -public class DataDomainQueryAction implements QueryRouter, OperationObserver { +class DataDomainQueryAction implements QueryRouter, OperationObserver { - static final boolean DONE = true; + private static final boolean DONE = true; - final DataContext context; - final DataDomain domain; + private final DataContext context; + private final DataDomain domain; final Query query; - final QueryMetadata metadata; - final AdhocObjectFactory objectFactory; - - DataRowStore cache; - QueryResponse response; - GenericResponse fullResponse; - Map> prefetchResultsByPath; - Map> queriesByNode; - Map queriesByExecutedQueries; - boolean noObjectConversion; + private final QueryMetadata metadata; + private final AdhocObjectFactory objectFactory; + + private DataRowStore cache; + private QueryResponse response; + private GenericResponse fullResponse; + private Map> prefetchResultsByPath; + private Map> queriesByNode; + private Map queriesByExecutedQueries; + private boolean noObjectConversion; private IteratedQueryResponse iteratedQueryResponse; /* From 115ab0514e0b85798067665155ed0a0bbc0af1fc Mon Sep 17 00:00:00 2001 From: Ivan Nikitka <70625960+Ivan-nikitko@users.noreply.github.com> Date: Fri, 6 Oct 2023 12:29:44 +0200 Subject: [PATCH 03/13] interface QueryResponse extended for ResultIterator using --- .../org/apache/cayenne/QueryResponse.java | 18 ++++++++++++++++++ .../apache/cayenne/access/DataContext.java | 2 +- .../cayenne/access/DataDomainQueryAction.java | 4 ++-- .../apache/cayenne/util/GenericResponse.java | 11 +++++++++++ .../cayenne/util/IteratedQueryResponse.java | 19 +++++++++++++++---- .../org/apache/cayenne/util/ListResponse.java | 11 +++++++++++ 6 files changed, 58 insertions(+), 7 deletions(-) diff --git a/cayenne-server/src/main/java/org/apache/cayenne/QueryResponse.java b/cayenne-server/src/main/java/org/apache/cayenne/QueryResponse.java index d301d4956f..4c4c282ee5 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/QueryResponse.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/QueryResponse.java @@ -70,6 +70,8 @@ public interface QueryResponse { /** * Returns whether current response is an iterator + * + * @since 5.0 */ boolean isIterator(); @@ -79,6 +81,13 @@ public interface QueryResponse { */ List currentList(); + /** + * Returns a current iterator. + * + * @since 5.0 + */ + ResultIterator currentIterator(); + /** * Returns an update count under the current iterator position. Returned value is an * int[] to accommodate batch queries. For a regular update result, the value will be @@ -105,6 +114,15 @@ public interface QueryResponse { @SuppressWarnings("rawtypes") List firstList(); + /** + * A utility method for quickly retrieving the Iterator in the response. Returns + * null if the query has no iterator. + * + * @since 5.0 + */ + @SuppressWarnings("rawtypes") + ResultIterator firstIterator(); + /** * A utility method for quickly retrieving the first update count from the response. * Note that this method resets current iterator to an undefined state. 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 998e0cc887..5b8a815fb5 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 @@ -936,7 +936,7 @@ public List performQuery(Query query) { query = nonNullDelegate().willPerformQuery(this, query); if (query instanceof IteratedQueryDecorator){ IteratedQueryResponse queryResponse = (IteratedQueryResponse) (onQuery(this, query)); - return Collections.singletonList(queryResponse.getIterator()); + return Collections.singletonList(queryResponse.currentIterator()); } if (query == null) { return new ArrayList<>(1); 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 2b9a8a3026..9b89a19e8c 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 @@ -538,13 +538,13 @@ private void runIteratedQuery(Transaction tx){ } private void wrapResponseIteratorWithTransactionDecorator(Transaction tx){ - ResultIterator iterator = iteratedQueryResponse.getIterator(); + ResultIterator iterator = iteratedQueryResponse.currentIterator(); TransactionResultIteratorDecorator decorator = new TransactionResultIteratorDecorator<>(iterator, tx); iteratedQueryResponse.setIterator(decorator); } private void wrapResponseIteratorWithConverterDecorator(ObjectConversionStrategy converter) { - ResultIterator iterator = ((IteratedQueryResponse) response).getIterator(); + ResultIterator iterator = response.currentIterator(); ResultIteratorConverterDecorator decorator = new ResultIteratorConverterDecorator(iterator, converter); ((IteratedQueryResponse) response).setIterator(decorator); } diff --git a/cayenne-server/src/main/java/org/apache/cayenne/util/GenericResponse.java b/cayenne-server/src/main/java/org/apache/cayenne/util/GenericResponse.java index 1f76968f23..018233357a 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/util/GenericResponse.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/util/GenericResponse.java @@ -24,6 +24,7 @@ import java.util.List; import org.apache.cayenne.QueryResponse; +import org.apache.cayenne.ResultIterator; /** * A simple serializable implementation of QueryResponse. @@ -79,6 +80,11 @@ public List firstList() { return null; } + @Override + public ResultIterator firstIterator() { + return null; + } + public int[] firstUpdateCount() { for (reset(); next();) { if (!isList()) { @@ -93,6 +99,11 @@ public List currentList() { return (List) results.get(currentIndex - 1); } + @Override + public ResultIterator currentIterator() { + return null; + } + public int[] currentUpdateCount() { return (int[]) results.get(currentIndex - 1); } 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 index c51e75ed03..2339cdab34 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/util/IteratedQueryResponse.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/util/IteratedQueryResponse.java @@ -23,6 +23,11 @@ import java.util.List; +/** + * Implementation of QueryResponse for iterated query. + * + * @since 5.0 + */ public class IteratedQueryResponse extends GenericResponse { private ResultIterator iterator; public IteratedQueryResponse(ResultIterator iterator) { @@ -38,10 +43,6 @@ public int size() { return -1; } - public ResultIterator getIterator() { - return iterator; - } - @Override public boolean isList() { return false; @@ -57,6 +58,11 @@ public List currentList() { return null; } + @Override + public ResultIterator currentIterator() { + return iterator; + } + @Override public int[] currentUpdateCount() { return new int[0]; @@ -72,6 +78,11 @@ public List firstList() { return null; } + @Override + public ResultIterator firstIterator() { + return iterator; + } + @Override public int[] firstUpdateCount() { return new int[0]; diff --git a/cayenne-server/src/main/java/org/apache/cayenne/util/ListResponse.java b/cayenne-server/src/main/java/org/apache/cayenne/util/ListResponse.java index cb66795230..30f878375c 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/util/ListResponse.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/util/ListResponse.java @@ -25,6 +25,7 @@ import java.util.List; import org.apache.cayenne.QueryResponse; +import org.apache.cayenne.ResultIterator; /** * A QueryResponse optimized to hold a single object or data row list. @@ -77,6 +78,11 @@ public List currentList() { return objectList; } + @Override + public ResultIterator currentIterator() { + return null; + } + public int[] currentUpdateCount() { throw new IllegalStateException("Current object is not an update count"); } @@ -94,6 +100,11 @@ public List firstList() { return objectList; } + @Override + public ResultIterator firstIterator() { + return null; + } + public int[] firstUpdateCount() { return new int[0]; } From bc396185a4343dad829204123d5a7be33d4d0f8f Mon Sep 17 00:00:00 2001 From: Ivan Nikitka <70625960+Ivan-nikitko@users.noreply.github.com> Date: Mon, 9 Oct 2023 17:44:05 +0200 Subject: [PATCH 04/13] cleanUp --- .../apache/cayenne/access/DataContext.java | 8 ++- .../ResultIteratorConverterDecorator.java | 54 +++++++------------ .../cayenne/util/IteratedQueryResponse.java | 2 +- 3 files changed, 22 insertions(+), 42 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 5b8a815fb5..eb59a0c73b 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,7 +798,9 @@ GraphDiff flushToParent(boolean cascade) { @Override public ResultIterator iterator(final Select query) { IteratedQueryDecorator queryDecorator = new IteratedQueryDecorator(query); - return (ResultIterator) performQuery(queryDecorator).get(0); + queryDecorator = (IteratedQueryDecorator) nonNullDelegate().willPerformQuery(this, queryDecorator); + IteratedQueryResponse queryResponse = (IteratedQueryResponse) (onQuery(this, queryDecorator)); + return queryResponse.currentIterator(); } /** @@ -934,10 +936,6 @@ public QueryResponse performGenericQuery(Query query) { @SuppressWarnings("unchecked") public List performQuery(Query query) { query = nonNullDelegate().willPerformQuery(this, query); - if (query instanceof IteratedQueryDecorator){ - IteratedQueryResponse queryResponse = (IteratedQueryResponse) (onQuery(this, query)); - return Collections.singletonList(queryResponse.currentIterator()); - } if (query == null) { return new ArrayList<>(1); } 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 3533df463d..9c26e62eef 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 @@ -23,14 +23,15 @@ import java.util.Iterator; import java.util.List; +import java.util.Objects; -public class ResultIteratorConverterDecorator implements ResultIterator { + class ResultIteratorConverterDecorator implements ResultIterator { private final ResultIterator iterator; private final DataDomainQueryAction.ObjectConversionStrategy converter; - public ResultIteratorConverterDecorator(ResultIterator iterator, DataDomainQueryAction.ObjectConversionStrategy converter) { - this.iterator = iterator; - this.converter = converter; + ResultIteratorConverterDecorator(ResultIterator iterator, DataDomainQueryAction.ObjectConversionStrategy converter) { + this.iterator = Objects.requireNonNull(iterator); + this.converter = Objects.requireNonNull(converter); } @Override @@ -45,10 +46,7 @@ public boolean hasNextRow() { @Override public Object nextRow() { - if (converter != null) { - return converter.convert(iterator.nextRow()); - } - return iterator.nextRow(); + return converter.convert(iterator.nextRow()); } @Override @@ -63,32 +61,16 @@ public void close() { @Override public Iterator iterator() { - return new IteratorConverterDecorator(iterator,converter); + return new Iterator() { + @Override + public boolean hasNext() { + return iterator.hasNextRow(); + } + + @Override + public Object next() { + return converter.convert(iterator.nextRow()); + } + }; } - -} - -class IteratorConverterDecorator implements Iterator{ - private final ResultIterator iterator; - private final DataDomainQueryAction.ObjectConversionStrategy converter; - - public IteratorConverterDecorator(ResultIterator iterator, DataDomainQueryAction.ObjectConversionStrategy converter) { - this.iterator = iterator; - this.converter = converter; - } - - @Override - public boolean hasNext() { - return iterator.hasNextRow(); - } - - @Override - public Object next() { - if (converter != null) { - return converter.convert(iterator.nextRow()); - } - return 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 index 2339cdab34..961af918e6 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/util/IteratedQueryResponse.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/util/IteratedQueryResponse.java @@ -59,7 +59,7 @@ public List currentList() { } @Override - public ResultIterator currentIterator() { + public ResultIterator currentIterator() { return iterator; } From 3337bb0523ebb5b2208e7e7d123c3a2fd45393de Mon Sep 17 00:00:00 2001 From: Ivan Nikitka <70625960+Ivan-nikitko@users.noreply.github.com> Date: Tue, 10 Oct 2023 09:11:27 +0200 Subject: [PATCH 05/13] prefetchDisjoint and DisjointById added --- .../cayenne/access/DataDomainQueryAction.java | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) 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 9b89a19e8c..37528e56b5 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 @@ -57,6 +57,8 @@ import org.apache.cayenne.util.IteratedQueryResponse; import org.apache.cayenne.util.ListResponse; import org.apache.cayenne.util.Util; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.Arrays; @@ -78,10 +80,12 @@ */ class DataDomainQueryAction implements QueryRouter, OperationObserver { + private static final Logger LOGGER = LoggerFactory.getLogger(DataDomainQueryAction.class); + private static final boolean DONE = true; private final DataContext context; - private final DataDomain domain; + final DataDomain domain; final Query query; private final QueryMetadata metadata; private final AdhocObjectFactory objectFactory; @@ -147,6 +151,16 @@ QueryResponse execute() { private boolean interceptIteratedQuery() { if (query instanceof IteratedQueryDecorator) { + if (metadata.getPrefetchTree() != null) { + for (PrefetchTreeNode prefetchTreeNode : metadata.getPrefetchTree().getChildren()) { + if (prefetchTreeNode.isDisjointPrefetch()) { + throw new CayenneRuntimeException("\"Disjoint\" semantic doesn't work with iterator. Use \"Joint\" instead"); + } + if (prefetchTreeNode.isDisjointByIdPrefetch()){ + LOGGER.warn("A separate select query will be created for each iterated item"); + } + } + } performIteratedQuery(); return DONE; } From 789a1b74f37c4cb61d0526d0623c9881678db87f Mon Sep 17 00:00:00 2001 From: Ivan Nikitka <70625960+Ivan-nikitko@users.noreply.github.com> Date: Tue, 10 Oct 2023 09:11:43 +0200 Subject: [PATCH 06/13] tests --- .../access/DataContextIteratedQueryIT.java | 14 + .../apache/cayenne/query/IteratedQueryIT.java | 184 --------- .../apache/cayenne/query/MappedQueryIT.java | 25 ++ .../query/ObjectSelectIteratedQueryIT.java | 356 ++++++++++++++++++ .../query/SQLSelectIteratedQueryIT.java | 268 +++++++++++++ .../query/SelectByIdIteratedQueryIT.java | 157 ++++++++ 6 files changed, 820 insertions(+), 184 deletions(-) delete mode 100644 cayenne-server/src/test/java/org/apache/cayenne/query/IteratedQueryIT.java create mode 100644 cayenne-server/src/test/java/org/apache/cayenne/query/ObjectSelectIteratedQueryIT.java create mode 100644 cayenne-server/src/test/java/org/apache/cayenne/query/SQLSelectIteratedQueryIT.java create mode 100644 cayenne-server/src/test/java/org/apache/cayenne/query/SelectByIdIteratedQueryIT.java diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/DataContextIteratedQueryIT.java b/cayenne-server/src/test/java/org/apache/cayenne/access/DataContextIteratedQueryIT.java index 8853c8c0d7..6aef0419d7 100644 --- a/cayenne-server/src/test/java/org/apache/cayenne/access/DataContextIteratedQueryIT.java +++ b/cayenne-server/src/test/java/org/apache/cayenne/access/DataContextIteratedQueryIT.java @@ -185,6 +185,20 @@ public void testPerformIteratedQuery_resolve() throws Exception { } } + @Test + public void testContextIterator() throws Exception { + createArtistsAndPaintingsDataSet(); + try (ResultIterator it = context + .iterator(ObjectSelect.query(Artist.class))) { + while (it.hasNextRow()) { + Artist artist = it.nextRow(); + List paintings = artist.getPaintingArray(); + assertNotNull(paintings); + assertEquals("Expected one painting for artist: " + artist, 1, paintings.size()); + } + } + } + @Test public void testPerformIteratedQuery_CommitWithinIterator() throws Exception { createArtistsAndPaintingsDataSet(); diff --git a/cayenne-server/src/test/java/org/apache/cayenne/query/IteratedQueryIT.java b/cayenne-server/src/test/java/org/apache/cayenne/query/IteratedQueryIT.java deleted file mode 100644 index 636e591c77..0000000000 --- a/cayenne-server/src/test/java/org/apache/cayenne/query/IteratedQueryIT.java +++ /dev/null @@ -1,184 +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.query; - -import org.apache.cayenne.ResultBatchIterator; -import org.apache.cayenne.ResultIterator; -import org.apache.cayenne.access.DataContext; -import org.apache.cayenne.di.Inject; -import org.apache.cayenne.test.jdbc.DBHelper; -import org.apache.cayenne.test.jdbc.TableHelper; -import org.apache.cayenne.testdo.testmap.Painting; -import org.apache.cayenne.unit.di.server.CayenneProjects; -import org.apache.cayenne.unit.di.server.ServerCase; -import org.apache.cayenne.unit.di.server.UseServerRuntime; -import org.junit.Before; -import org.junit.Test; - -import java.sql.SQLException; -import java.sql.Types; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - - -@UseServerRuntime(CayenneProjects.TESTMAP_PROJECT) -public class IteratedQueryIT extends ServerCase { - - @Inject - private DataContext context; - - @Inject - private DBHelper dbHelper; - - private TableHelper tPainting; - - private TableHelper tArtist; - - - @Before - public void before() throws Exception { - tPainting = new TableHelper(dbHelper, "PAINTING") - .setColumns("PAINTING_ID", "PAINTING_TITLE", "ESTIMATED_PRICE", "ARTIST_ID") - .setColumnTypes(Types.INTEGER, Types.VARCHAR, Types.DECIMAL, Types.INTEGER); - - tArtist = new TableHelper(dbHelper, "ARTIST") - .setColumns("ARTIST_ID", "ARTIST_NAME"); - - createArtistDataSet(); - createPaintingsDataSet(); - - } - - private void createPaintingsDataSet() throws Exception { - for (int i = 1; i <= 20; i++) { - tPainting.insert(i, "painting" + i, 10000. * i, 1); - } - } - - private void createArtistDataSet() throws SQLException { - tArtist.insert(1, "Test1"); - tArtist.insert(2, "Test2"); - } - - - //SingleObjectConversation - @Test - public void test_prefetchWithBatchIterator() { - Painting painting; - try (ResultBatchIterator iterator = ObjectSelect.query(Painting.class) - .prefetch(Painting.TO_ARTIST.joint()) - .batchIterator(context, 5)) { - painting = iterator.next().get(0); - } - assertTrue(painting instanceof Painting); - } - - @Test - public void test_prefetchWithIterator() { - Painting painting; - try (ResultIterator iterator = ObjectSelect.query(Painting.class) - .prefetch(Painting.TO_ARTIST.joint()) - .iterator(context)) { - painting = iterator.nextRow(); - } - assertTrue(painting instanceof Painting); - } - - //SingleScalarConversationStrategy - @Test - public void test_ScalarQueryWithBatchIterator() { - int id; - try (ResultBatchIterator iterator = SQLSelect.scalarQuery("SELECT PAINTING_ID FROM PAINTING ORDER BY PAINTING_ID", - Integer.class).batchIterator(context, 2)) { - id = iterator.next().get(0); - } - assertEquals(1, id); - } - - //SingleScalarConversationStrategy - @Test - public void test_ScalarQueryWithIterator() { - int id; - try (ResultIterator iterator = SQLSelect.scalarQuery("SELECT PAINTING_ID FROM PAINTING ORDER BY PAINTING_ID", - Integer.class).iterator(context)) { - id = iterator.nextRow(); - } - assertEquals(1, id); - } - - //MapperConversationStrategy - //MixedConversationStrategy - @Test - public void test_MappingWithBatchIterator() { - try (ResultBatchIterator batchIterator = ObjectSelect.columnQuery(Painting.class, Painting.PAINTING_TITLE, Painting.ESTIMATED_PRICE) - .map(this::toDto) - .batchIterator(context, 2)) { - - assertTrue(batchIterator.iterator().next().get(0) instanceof DTO); - } - context.commitChanges(); - } - - //MapperConversationStrategy - //MixedConversationStrategy - @Test - public void test_MappingWithIterator() { - try (ResultIterator iterator = ObjectSelect.columnQuery(Painting.class, Painting.PAINTING_TITLE, Painting.ESTIMATED_PRICE) - .map(this::toDto) - .iterator(context)) { - - assertTrue(iterator.nextRow() instanceof DTO); - } - context.commitChanges(); - } - - - //SingleObjectConversation - @Test - public void UnclosedTransactionsTest() { - for (int i = 0; i < 5; i++) { - test_MappingWithBatchIterator(); - } - } - - - DTO toDto(Object[] data) { - return new DTO(data); - } - - - static class DTO { - private final String title; - private final Long estimatedPrice; - - public DTO(Object[] data) { - this.title = (String) data[0]; - this.estimatedPrice = ((Number) data[1]).longValue(); - } - - @Override - public String toString() { - return "DTO{" + - "title='" + title + '\'' + - ", estimatedPrice=" + estimatedPrice + - '}'; - } - } -} diff --git a/cayenne-server/src/test/java/org/apache/cayenne/query/MappedQueryIT.java b/cayenne-server/src/test/java/org/apache/cayenne/query/MappedQueryIT.java index 03f95a5457..9fe1254578 100644 --- a/cayenne-server/src/test/java/org/apache/cayenne/query/MappedQueryIT.java +++ b/cayenne-server/src/test/java/org/apache/cayenne/query/MappedQueryIT.java @@ -20,8 +20,10 @@ import org.apache.cayenne.DataRow; import org.apache.cayenne.QueryResponse; +import org.apache.cayenne.ResultBatchIterator; import org.apache.cayenne.access.DataContext; import org.apache.cayenne.di.Inject; +import org.apache.cayenne.exp.ExpressionFactory; import org.apache.cayenne.log.JdbcEventLogger; import org.apache.cayenne.test.jdbc.DBHelper; import org.apache.cayenne.test.jdbc.TableHelper; @@ -78,6 +80,29 @@ public void testSelectQuery() throws Exception { assertEquals("artist14", a.getArtistName()); } + @Test + public void testButchIterator() throws Exception { + createArtistsDataSet(); + + try (ResultBatchIterator iterator = MappedSelect + .query("ParameterizedQueryWithSharedCache", Artist.class) + .param("name", "artist14") + .batchIterator(context, 1)) { + int count = 0; + while (iterator.hasNext()) { + count++; + List artists = iterator.next(); + for (Artist artist : artists) { + //noinspection ConstantConditions + assertTrue(artist instanceof Artist); + assertEquals("artist14", artist.readPropertyDirectly("artistName")); + } + } + assertEquals(1,count); + } + + } + @Test public void testSQLTemplateSelect() throws Exception { createArtistsDataSet(); diff --git a/cayenne-server/src/test/java/org/apache/cayenne/query/ObjectSelectIteratedQueryIT.java b/cayenne-server/src/test/java/org/apache/cayenne/query/ObjectSelectIteratedQueryIT.java new file mode 100644 index 0000000000..b6f5a3db5b --- /dev/null +++ b/cayenne-server/src/test/java/org/apache/cayenne/query/ObjectSelectIteratedQueryIT.java @@ -0,0 +1,356 @@ +/***************************************************************** + * 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.query; + +import org.apache.cayenne.CayenneRuntimeException; +import org.apache.cayenne.DataRow; +import org.apache.cayenne.ResultBatchIterator; +import org.apache.cayenne.ResultIterator; +import org.apache.cayenne.access.DataContext; +import org.apache.cayenne.di.Inject; +import org.apache.cayenne.test.jdbc.DBHelper; +import org.apache.cayenne.test.jdbc.TableHelper; +import org.apache.cayenne.testdo.testmap.Artist; +import org.apache.cayenne.testdo.testmap.Painting; +import org.apache.cayenne.unit.di.server.CayenneProjects; +import org.apache.cayenne.unit.di.server.ServerCase; +import org.apache.cayenne.unit.di.server.UseServerRuntime; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import java.sql.SQLException; +import java.sql.Types; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + + +@UseServerRuntime(CayenneProjects.TESTMAP_PROJECT) +public class ObjectSelectIteratedQueryIT extends ServerCase { + + @Inject + private DataContext context; + + @Inject + private DBHelper dbHelper; + + private TableHelper tPainting; + + private TableHelper tArtist; + + + @Before + public void before() throws Exception { + tPainting = new TableHelper(dbHelper, "PAINTING") + .setColumns("PAINTING_ID", "PAINTING_TITLE", "ESTIMATED_PRICE", "ARTIST_ID") + .setColumnTypes(Types.INTEGER, Types.VARCHAR, Types.DECIMAL, Types.INTEGER); + + tArtist = new TableHelper(dbHelper, "ARTIST") + .setColumns("ARTIST_ID", "ARTIST_NAME"); + + createArtistDataSet(); + createPaintingsDataSet(); + + } + + private void createPaintingsDataSet() throws Exception { + for (int i = 1; i <= 20; i++) { + tPainting.insert(i, "painting" + i, 10000. * i, 1); + } + } + + private void createArtistDataSet() throws SQLException { + tArtist.insert(1, "Test1"); + tArtist.insert(2, "Test2"); + } + + @Test + public void prefetchWithBatchIterator() { + try (ResultBatchIterator iterator = ObjectSelect + .query(Painting.class) + .prefetch(Painting.TO_ARTIST.joint()) + .batchIterator(context, 10)) { + int count = 0; + while (iterator.hasNext()) { + count++; + List paintings = iterator.next(); + for (Painting painting : paintings) { + //noinspection ConstantConditions + assertTrue(painting instanceof Painting); + assertEquals("Test1", painting.getToArtist().readPropertyDirectly("artistName")); + } + } + assertEquals(2,count); + } + } + + @Test + public void queryPrefetchJointWithIterator() { + try (ResultIterator iterator = ObjectSelect + .query(Painting.class) + .prefetch(Painting.TO_ARTIST.joint()) + .iterator(context)) { + int count = 0; + while (iterator.hasNextRow()) { + count++; + Painting painting = iterator.nextRow(); + //noinspection ConstantConditions + assertTrue(painting instanceof Painting); + assertEquals("Test1", painting.getToArtist().readPropertyDirectly("artistName")); + } + assertEquals(20,count); + } + } + + @Test(expected = CayenneRuntimeException.class) + public void queryPrefetchDisjointWithIterator() { + try (ResultIterator iterator = ObjectSelect + .query(Painting.class) + .prefetch(Painting.TO_ARTIST.disjoint()) + .iterator(context)) { + iterator.nextRow(); + } + } + + + @Test(expected = CayenneRuntimeException.class) + public void queryPrefetchDisjointWithBatchIterator() { + try (ResultBatchIterator iterator = ObjectSelect + .query(Painting.class) + .prefetch(Painting.TO_ARTIST.disjoint()) + .batchIterator(context, 3)) { + iterator.next(); + } + } + + @Test + public void queryPrefetchDisjointByIdWithBIterator() { + try (ResultIterator iterator = ObjectSelect + .query(Painting.class) + .prefetch(Painting.TO_ARTIST.disjointById()) + .iterator(context)) { + int count = 0; + while (iterator.hasNextRow()) { + count++; + Painting painting = iterator.nextRow(); + //noinspection ConstantConditions + assertTrue(painting instanceof Painting); + assertEquals("Test1", painting.getToArtist().readPropertyDirectly("artistName")); + } + assertEquals(20,count); + } + } + + @Test + public void queryPrefetchJointWithBatchIterator() { + try (ResultBatchIterator iterator = ObjectSelect + .query(Painting.class,"Painting") + .prefetch(Painting.TO_ARTIST.joint()) + .batchIterator(context, 5)) { + int count = 0; + int paintingCounter = 0; + while (iterator.hasNext()) { + count++; + List paintingList = iterator.next(); + for (Painting painting : paintingList) { + paintingCounter++; + assertEquals("painting" + paintingCounter, painting.getPaintingTitle()); + assertEquals("Test1", painting.getToArtist().readPropertyDirectly("artistName")); + } + } + assertEquals(4, count); + } + } + + @Test + public void QueryWithIterator() { + try (ResultIterator iterator = ObjectSelect + .query(Painting.class, "Painting") + .prefetch(Painting.TO_ARTIST.joint()) + .iterator(context)) { + int count = 0; + int paintingCounter = 0; + while (iterator.hasNextRow()) { + count++; + Painting painting = iterator.nextRow(); + paintingCounter++; + assertEquals("painting" + paintingCounter, painting.getPaintingTitle()); + assertEquals("Test1", painting.getToArtist().readPropertyDirectly("artistName")); + } + assertEquals(20, count); + } + } + + @Test + public void mappingWithBatchIterator() { + try (ResultBatchIterator iterator = ObjectSelect + .columnQuery(Painting.class, Painting.PAINTING_TITLE, Painting.ESTIMATED_PRICE) + .map(this::toDto) + .batchIterator(context, 5)) { + int count = 0; + while (iterator.hasNext()) { + count++; + List dtos = iterator.next(); + for (DTO dto : dtos) { + //noinspection ConstantConditions + assertTrue(dto instanceof DTO); + assertTrue(dto.getTitle().contains("dto_painting")); + } + } + assertEquals(5, iterator.getBatchSize()); + assertEquals(4,count); + } + } + + @Test + public void mappingWithIterator() { + try (ResultIterator iterator = ObjectSelect + .columnQuery(Painting.class, Painting.PAINTING_TITLE, Painting.ESTIMATED_PRICE) + .map(this::toDto) + .iterator(context)) { + int count = 0; + while (iterator.hasNextRow()) { + count++; + DTO dto = iterator.nextRow(); + //noinspection ConstantConditions + assertTrue(dto instanceof DTO); + assertTrue(dto.getTitle().contains("dto_painting")); + } + assertEquals(20, count); + } + } + + @Test + public void dataRowQueryWithBatchIterator() { + try (ResultBatchIterator iterator = ObjectSelect + .dataRowQuery(Painting.class) + .batchIterator(context, 5)) { + int count = 0; + int paintingCounter = 0; + while (iterator.hasNext()) { + count++; + List rows = iterator.next(); + for (Object row : rows) { + paintingCounter++; + Painting painting = context.objectFromDataRow(Painting.class, (DataRow) row); + assertEquals("painting" + paintingCounter, painting.getPaintingTitle()); + } + } + assertEquals(4, count); + } + } + + @Test + public void dataRowQueryWithIterator() { + try (ResultIterator iterator = ObjectSelect + .dataRowQuery(Painting.class) + .iterator(context)) { + int count = 0; + int paintingCounter = 0; + while (iterator.hasNextRow()) { + count++; + paintingCounter++; + Object row = iterator.nextRow(); + Painting painting = context.objectFromDataRow(Painting.class, (DataRow) row); + assertEquals("painting" + paintingCounter, painting.getPaintingTitle()); + } + assertEquals(20, count); + } + } + + @Test + public void dbQueryWithIterator() { + try (ResultIterator iterator = ObjectSelect + .dbQuery("PAINTING") + .iterator(context)) { + int count = 0; + int paintingCounter = 0; + while (iterator.hasNextRow()) { + count++; + paintingCounter++; + Object row = iterator.nextRow(); + Painting painting = context.objectFromDataRow(Painting.class, (DataRow) row); + assertEquals("painting" + paintingCounter, painting.getPaintingTitle()); + } + assertEquals(20, count); + } + } + + @Test + public void dbQueryWithBatchIterator() { + try (ResultBatchIterator iterator = ObjectSelect + .dbQuery("PAINTING") + .batchIterator(context, 5)) { + int count = 0; + int paintingCounter = 0; + while (iterator.hasNext()) { + count++; + List rows = iterator.next(); + for (Object row : rows) { + paintingCounter++; + Painting painting = context.objectFromDataRow(Painting.class, (DataRow) row); + assertEquals("painting" + paintingCounter, painting.getPaintingTitle()); + } + } + assertEquals(4, count); + } + } + + + @Test + public void unclosedTransactionsTest() { + for (int i = 0; i < 10; i++) { + mappingWithBatchIterator(); + } + } + + DTO toDto(Object[] data) { + return new DTO(data); + } + + + static class DTO { + private final String title; + private final Long estimatedPrice; + + public DTO(Object[] data) { + this.title = "dto_" + (String) data[0]; + this.estimatedPrice = ((Number) data[1]).longValue(); + } + + public String getTitle() { + return title; + } + + public Long getEstimatedPrice() { + return estimatedPrice; + } + + @Override + public String toString() { + return "DTO{" + + "title='" + title + '\'' + + ", estimatedPrice=" + estimatedPrice + + '}'; + } + } +} diff --git a/cayenne-server/src/test/java/org/apache/cayenne/query/SQLSelectIteratedQueryIT.java b/cayenne-server/src/test/java/org/apache/cayenne/query/SQLSelectIteratedQueryIT.java new file mode 100644 index 0000000000..f38017fb3c --- /dev/null +++ b/cayenne-server/src/test/java/org/apache/cayenne/query/SQLSelectIteratedQueryIT.java @@ -0,0 +1,268 @@ +/***************************************************************** + * 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.query; + +import org.apache.cayenne.DataRow; +import org.apache.cayenne.ResultBatchIterator; +import org.apache.cayenne.ResultIterator; +import org.apache.cayenne.access.DataContext; +import org.apache.cayenne.di.Inject; +import org.apache.cayenne.test.jdbc.DBHelper; +import org.apache.cayenne.test.jdbc.TableHelper; +import org.apache.cayenne.testdo.testmap.Painting; +import org.apache.cayenne.unit.di.server.CayenneProjects; +import org.apache.cayenne.unit.di.server.ServerCase; +import org.apache.cayenne.unit.di.server.UseServerRuntime; +import org.junit.Before; +import org.junit.Test; + +import java.sql.SQLException; +import java.sql.Types; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + + +@UseServerRuntime(CayenneProjects.TESTMAP_PROJECT) +public class SQLSelectIteratedQueryIT extends ServerCase { + + @Inject + private DataContext context; + + @Inject + private DBHelper dbHelper; + + private TableHelper tPainting; + + private TableHelper tArtist; + + + @Before + public void before() throws Exception { + tPainting = new TableHelper(dbHelper, "PAINTING") + .setColumns("PAINTING_ID", "PAINTING_TITLE", "ESTIMATED_PRICE", "ARTIST_ID") + .setColumnTypes(Types.INTEGER, Types.VARCHAR, Types.DECIMAL, Types.INTEGER); + + tArtist = new TableHelper(dbHelper, "ARTIST") + .setColumns("ARTIST_ID", "ARTIST_NAME"); + + createArtistDataSet(); + createPaintingsDataSet(); + + } + + private void createPaintingsDataSet() throws Exception { + for (int i = 1; i <= 20; i++) { + tPainting.insert(i, "painting" + i, 10000. * i, 1); + } + } + + private void createArtistDataSet() throws SQLException { + tArtist.insert(1, "Test1"); + tArtist.insert(2, "Test2"); + } + + @Test + public void scalarQueryWithBatchIterator() { + try (ResultBatchIterator iterator = SQLSelect + .scalarQuery("SELECT PAINTING_ID FROM PAINTING ORDER BY PAINTING_ID", Integer.class) + .batchIterator(context, 5)) { + int count = 0; + Integer expectedId = 0; + while (iterator.hasNext()) { + count++; + List ids = iterator.next(); + for (Integer id : ids) { + expectedId++; + assertEquals(expectedId, id); + } + } + assertEquals(4, count); + } + } + + @Test + public void scalarQueryWithIterator() { + try (ResultIterator iterator = SQLSelect + .scalarQuery("SELECT PAINTING_ID FROM PAINTING ORDER BY PAINTING_ID", Integer.class) + .iterator(context)) { + int count = 0; + Integer expectedId = 0; + while (iterator.hasNextRow()) { + count++; + expectedId++; + Integer id = iterator.nextRow(); + assertEquals(expectedId, id); + } + assertEquals(20, count); + } + } + + @Test + public void dataRowQueryWithBatchIterator() { + try (ResultBatchIterator iterator = SQLSelect + .dataRowQuery("SELECT * FROM PAINTING") + .batchIterator(context, 5)) { + int count = 0; + int paintingCounter = 0; + while (iterator.hasNext()) { + count++; + List rows = iterator.next(); + for (Object row : rows) { + paintingCounter++; + Painting painting = context.objectFromDataRow(Painting.class, (DataRow) row); + assertEquals("painting" + paintingCounter, painting.getPaintingTitle()); + } + } + assertEquals(4, count); + } + } + + @Test + public void dataRowQueryWithIterator() { + try (ResultIterator iterator = SQLSelect + .dataRowQuery("SELECT * FROM PAINTING") + .iterator(context)) { + int count = 0; + int paintingCounter = 0; + while (iterator.hasNextRow()) { + count++; + paintingCounter++; + Object row = iterator.nextRow(); + Painting painting = context.objectFromDataRow(Painting.class, (DataRow) row); + assertEquals("painting" + paintingCounter, painting.getPaintingTitle()); + } + assertEquals(20, count); + } + } + + @Test + public void QueryWithBatchIterator() { + try (ResultBatchIterator iterator = SQLSelect + .query(Painting.class,"SELECT * FROM PAINTING") + .batchIterator(context, 5)) { + int count = 0; + int paintingCounter = 0; + while (iterator.hasNext()) { + count++; + List paintingList = iterator.next(); + for (Painting painting : paintingList) { + paintingCounter++; + assertEquals("painting" + paintingCounter, painting.getPaintingTitle()); + } + } + assertEquals(4, count); + } + } + + @Test + public void QueryWithIterator() { + try (ResultIterator iterator = SQLSelect + .query(Painting.class, "SELECT * FROM PAINTING") + .iterator(context)) { + int count = 0; + int paintingCounter = 0; + while (iterator.hasNextRow()) { + count++; + Painting painting = iterator.nextRow(); + paintingCounter++; + assertEquals("painting" + paintingCounter, painting.getPaintingTitle()); + } + assertEquals(20, count); + } + } + + //MapperConversationStrategy + //MixedConversationStrategy + @Test + public void MappingWithBatchIterator() { + try (ResultBatchIterator iterator = SQLSelect + .columnQuery( "SELECT PAINTING_TITLE, ESTIMATED_PRICE FROM PAINTING") + .map(this::toDto) + .batchIterator(context, 5)) { + int count = 0; + while (iterator.hasNext()) { + count++; + List dtoList = iterator.next(); + for (DTO dto : dtoList) { + //noinspection ConstantConditions + assertTrue(dto instanceof DTO); + assertTrue(dto.getTitle().contains("dto_painting")); + } + } + assertEquals(5, iterator.getBatchSize()); + assertEquals(4,count); + } + } + + //MapperConversationStrategy + //MixedConversationStrategy + @Test + public void MappingWithIterator() { + try (ResultIterator iterator = SQLSelect + .columnQuery("SELECT PAINTING_TITLE, ESTIMATED_PRICE FROM PAINTING") + .map(this::toDto) + .iterator(context)) { + int count = 0; + while (iterator.hasNextRow()) { + count++; + DTO dto = iterator.nextRow(); + //noinspection ConstantConditions + assertTrue(dto instanceof DTO); + assertTrue(dto.getTitle().contains("dto_painting")); + } + assertEquals(20, count); + } + } + + + DTO toDto(Object[] data) { + return new DTO(data); + } + + + static class DTO { + private final String title; + private final Long estimatedPrice; + + public DTO(Object[] data) { + this.title = "dto_" + (String) data[0]; + this.estimatedPrice = ((Number) data[1]).longValue(); + } + + public String getTitle() { + return title; + } + + public Long getEstimatedPrice() { + return estimatedPrice; + } + + @Override + public String toString() { + return "DTO{" + + "title='" + title + '\'' + + ", estimatedPrice=" + estimatedPrice + + '}'; + } + } +} diff --git a/cayenne-server/src/test/java/org/apache/cayenne/query/SelectByIdIteratedQueryIT.java b/cayenne-server/src/test/java/org/apache/cayenne/query/SelectByIdIteratedQueryIT.java new file mode 100644 index 0000000000..afa32d1a08 --- /dev/null +++ b/cayenne-server/src/test/java/org/apache/cayenne/query/SelectByIdIteratedQueryIT.java @@ -0,0 +1,157 @@ +/***************************************************************** + * 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.query; + +import org.apache.cayenne.DataRow; +import org.apache.cayenne.ResultBatchIterator; +import org.apache.cayenne.ResultIterator; +import org.apache.cayenne.access.DataContext; +import org.apache.cayenne.di.Inject; +import org.apache.cayenne.test.jdbc.DBHelper; +import org.apache.cayenne.test.jdbc.TableHelper; +import org.apache.cayenne.testdo.testmap.Painting; +import org.apache.cayenne.unit.di.server.CayenneProjects; +import org.apache.cayenne.unit.di.server.ServerCase; +import org.apache.cayenne.unit.di.server.UseServerRuntime; +import org.junit.Before; +import org.junit.Test; + +import java.sql.SQLException; +import java.sql.Types; +import java.util.Arrays; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + + +@UseServerRuntime(CayenneProjects.TESTMAP_PROJECT) +public class SelectByIdIteratedQueryIT extends ServerCase { + + @Inject + private DataContext context; + + @Inject + private DBHelper dbHelper; + + private TableHelper tPainting; + + private TableHelper tArtist; + + + @Before + public void before() throws Exception { + tPainting = new TableHelper(dbHelper, "PAINTING") + .setColumns("PAINTING_ID", "PAINTING_TITLE", "ESTIMATED_PRICE", "ARTIST_ID") + .setColumnTypes(Types.INTEGER, Types.VARCHAR, Types.DECIMAL, Types.INTEGER); + + tArtist = new TableHelper(dbHelper, "ARTIST") + .setColumns("ARTIST_ID", "ARTIST_NAME"); + + createArtistDataSet(); + createPaintingsDataSet(); + + } + + private void createPaintingsDataSet() throws Exception { + for (int i = 1; i <= 20; i++) { + tPainting.insert(i, "painting" + i, 10000. * i, 1); + } + } + + private void createArtistDataSet() throws SQLException { + tArtist.insert(1, "Test1"); + tArtist.insert(2, "Test2"); + } + + @Test + public void queryWithButchIterator() { + try (ResultBatchIterator iterator = SelectById + .query(Painting.class, Arrays.asList(1,2,3,4,5,6)) + .batchIterator(context, 3)){ + int count = 0; + int paintingCounter = 0; + while (iterator.hasNext()) { + count++; + List paintingList = iterator.next(); + for (Painting painting : paintingList) { + paintingCounter++; + assertEquals("painting" + paintingCounter, painting.getPaintingTitle()); + } + } + assertEquals(2, count); + } + } + + @Test + public void queryWithIterator() { + try (ResultIterator iterator = SelectById + .query(Painting.class, Arrays.asList(1,2,3,4,5,6)) + .iterator(context)){ + int count = 0; + int paintingCounter = 0; + while (iterator.hasNextRow()) { + count++; + Painting painting = iterator.nextRow(); + paintingCounter++; + assertEquals("painting" + paintingCounter, painting.getPaintingTitle()); + } + assertEquals(6, count); + } + } + + @Test + public void dataRowQueryWithBatchIterator() { + try (ResultBatchIterator iterator = SelectById + .dataRowQuery(Painting.class, 1, 2,3,4,5,6) + .batchIterator(context, 3)) { + int count = 0; + int paintingCounter = 0; + while (iterator.hasNext()) { + count++; + List rows = iterator.next(); + for (Object row : rows) { + paintingCounter++; + Painting painting = context.objectFromDataRow(Painting.class, (DataRow) row); + assertEquals("painting" + paintingCounter, painting.getPaintingTitle()); + } + } + assertEquals(2, count); + } + } + + @Test + public void dataRowQueryWithIterator() { + try (ResultIterator iterator = SelectById + .dataRowQuery(Painting.class, 1, 2,3,4,5,6) + .iterator(context)) { + int count = 0; + int paintingCounter = 0; + while (iterator.hasNextRow()) { + count++; + paintingCounter++; + Object row = iterator.nextRow(); + Painting painting = context.objectFromDataRow(Painting.class, (DataRow) row); + assertEquals("painting" + paintingCounter, painting.getPaintingTitle()); + } + assertEquals(6, count); + } + } + +} From 75c8cf92fc023601621978c7814dc4a7f2b44941 Mon Sep 17 00:00:00 2001 From: Ivan Nikitka <70625960+Ivan-nikitko@users.noreply.github.com> Date: Wed, 11 Oct 2023 20:01:48 +0200 Subject: [PATCH 07/13] generics defined, cleanUp --- .../apache/cayenne/access/DataContext.java | 74 +++---------------- .../cayenne/access/DataDomainQueryAction.java | 2 +- 2 files changed, 11 insertions(+), 65 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 eb59a0c73b..002a550492 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 @@ -65,8 +65,6 @@ import org.apache.cayenne.tx.TransactionFactory; import org.apache.cayenne.util.EventUtil; import org.apache.cayenne.util.GenericResponse; -import org.apache.cayenne.util.IteratedQueryResponse; -import org.apache.cayenne.util.ResultIteratorIterator; import org.apache.cayenne.util.Util; /** @@ -181,7 +179,7 @@ public DataDomain getParentDataDomain() { return (DataDomain) channel; } - List response = channel.onQuery(this, new DataDomainQuery()).firstList(); + List response = channel.onQuery(this, new DataDomainQuery()).firstList(); if (response != null && response.size() > 0 && response.get(0) instanceof DataDomain) { return (DataDomain) response.get(0); @@ -278,7 +276,7 @@ public Collection uncommittedObjects() { // guess target collection size Collection objects = new ArrayList<>(len > 100 ? len / 2 : len); - Iterator it = getObjectStore().getObjectIterator(); + Iterator it = getObjectStore().getObjectIterator(); while (it.hasNext()) { Persistent object = (Persistent) it.next(); int state = object.getPersistenceState(); @@ -421,7 +419,7 @@ public List objectsFromDataRows(ClassDescriptor descriptor, List dataRows) { + private List objectsFromDataRowsFromParentContext(ClassDescriptor descriptor, List dataRows) { return getChannel().onQuery(this, new ObjectsFromDataRowsQuery(descriptor, dataRows)).firstList(); } @@ -569,9 +567,9 @@ public boolean visitToMany(ToManyProperty property) { Object value = property.readProperty(persistent); @SuppressWarnings("unchecked") - Collection collection = (value instanceof Map) + Collection> collection = (value instanceof Map) ? ((Map) value).entrySet() - : (Collection) value; + : (Collection>) value; for (Object target : collection) { if (target instanceof Persistent) { @@ -613,7 +611,7 @@ public boolean visitAttribute(AttributeProperty property) { * * @see #invalidateObjects(Collection) */ - public void unregisterObjects(Collection dataObjects) { + public void unregisterObjects(Collection dataObjects) { getObjectStore().objectsUnregistered(dataObjects); } @@ -872,7 +870,7 @@ public ResultIterator performIteratedQuery(Query query) { if (tx.isRollbackOnly()) { try { tx.rollback(); - } catch (Exception rollbackEx) { + } catch (Exception ignored) { } } } @@ -884,7 +882,7 @@ public ResultIterator performIteratedQuery(Query query) { /** * Runs an iterated query in a transactional context provided by the caller. */ - ResultIterator internalPerformIteratedQuery(Query query) { + ResultIterator internalPerformIteratedQuery(Query query) { // note that for now DataChannel API does not support cursors (aka // ResultIterator), so we have to go directly to the DataDomain. IteratedSelectObserver observer = new IteratedSelectObserver(); @@ -933,14 +931,13 @@ public QueryResponse performGenericQuery(Query query) { * Сan also return an iterator if the query is an instance of iteratedQuery. */ @Override - @SuppressWarnings("unchecked") public List performQuery(Query query) { query = nonNullDelegate().willPerformQuery(this, query); if (query == null) { return new ArrayList<>(1); } - List result = onQuery(this, query).firstList(); + List result = onQuery(this, query).firstList(); return result != null ? result : new ArrayList<>(1); } @@ -1021,7 +1018,7 @@ public List performQuery(String queryName, boolean expireCachedLists) { * is required in case a query uses caching. * @since 1.1 */ - public List performQuery(String queryName, Map parameters, boolean expireCachedLists) { + public List performQuery(String queryName, Map parameters, boolean expireCachedLists) { return (List) performQuery(expireCachedLists ? MappedSelect.query(queryName).params(parameters).forceNoCache() : MappedSelect.query(queryName).params(parameters)); @@ -1185,55 +1182,4 @@ public void setTransactionFactory(TransactionFactory transactionFactory) { this.transactionFactory = transactionFactory; } - /** - * ResultIterator that can convert DataRow to Persistent object on the fly. - */ - static class DataRowResultIterator implements ResultIterator { - - final ResultIterator rows; - ObjectResolver resolver; - - DataRowResultIterator(ResultIterator rows, ObjectResolver resolver) { - this.rows = rows; - this.resolver = resolver; - } - - @Override - public Iterator iterator() { - return new ResultIteratorIterator<>(this); - } - - @Override - public List allRows() { - List list = new ArrayList<>(); - while (hasNextRow()) { - list.add(nextRow()); - } - return list; - } - - @Override - public boolean hasNextRow() { - return rows.hasNextRow(); - } - - @Override - @SuppressWarnings("unchecked") - public T nextRow() { - DataRow row = (DataRow) rows.nextRow(); - List objects = (List) resolver.synchronizedObjectsFromDataRows(Collections.singletonList(row)); - return objects.get(0); - } - - @Override - public void skipRow() { - rows.skipRow(); - } - - @Override - public void close() { - rows.close(); - } - } - } 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 37528e56b5..7ab440f0a9 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 @@ -183,7 +183,7 @@ private void performIteratedQuery() { if (tx.isRollbackOnly()) { try { tx.rollback(); - } catch (Exception rollbackEx) { + } catch (Exception ignored) { } } } From c63c776f9c945e1183bba7ed4fc5a26c53c39ad1 Mon Sep 17 00:00:00 2001 From: Ivan Nikitka <70625960+Ivan-nikitko@users.noreply.github.com> Date: Wed, 11 Oct 2023 20:03:38 +0200 Subject: [PATCH 08/13] iteratedQuery with pagination --- .../apache/cayenne/access/DataContext.java | 22 ++---------- .../cayenne/access/DataDomainQueryAction.java | 35 +++++++++++-------- .../util/ObjectContextQueryAction.java | 28 ++++++++------- .../query/ObjectSelectIteratedQueryIT.java | 12 +++++++ 4 files changed, 52 insertions(+), 45 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 002a550492..6d974b5a5b 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 @@ -796,27 +796,11 @@ GraphDiff flushToParent(boolean cascade) { @Override public ResultIterator iterator(final Select query) { IteratedQueryDecorator queryDecorator = new IteratedQueryDecorator(query); - queryDecorator = (IteratedQueryDecorator) nonNullDelegate().willPerformQuery(this, queryDecorator); - IteratedQueryResponse queryResponse = (IteratedQueryResponse) (onQuery(this, queryDecorator)); - return queryResponse.currentIterator(); + Query queryToRun = nonNullDelegate().willPerformQuery(this, queryDecorator); + QueryResponse queryResponse = (onQuery(this, queryToRun)); + return (ResultIterator) queryResponse.currentIterator(); } - /** - * This method repeats logic of DataDomainQueryAction.interceptObjectConversion() method. - * The difference is that iterator(or batchIterator) doesn't support "mixed" results. - */ - private boolean isObjectArrayResult(QueryMetadata md) { - List resultMapping = md.getResultSetMapping(); - if(resultMapping == null) { - return false; - } - - if (md.isSingleResultSetMapping()) { - return !(resultMapping.get(0) instanceof EntityResultSegment); - } else { - return true; - } - } /** * Performs a single database select query returning result as a 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 7ab440f0a9..6a78a29911 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 @@ -151,22 +151,30 @@ QueryResponse execute() { private boolean interceptIteratedQuery() { if (query instanceof IteratedQueryDecorator) { - if (metadata.getPrefetchTree() != null) { - for (PrefetchTreeNode prefetchTreeNode : metadata.getPrefetchTree().getChildren()) { - if (prefetchTreeNode.isDisjointPrefetch()) { - throw new CayenneRuntimeException("\"Disjoint\" semantic doesn't work with iterator. Use \"Joint\" instead"); - } - if (prefetchTreeNode.isDisjointByIdPrefetch()){ - LOGGER.warn("A separate select query will be created for each iterated item"); - } - } - } + validateIteratedQuery(); performIteratedQuery(); return DONE; } return !DONE; } + private void validateIteratedQuery() { + System.out.println("Validate"); + /* if (metadata.getPageSize() != 0){ + throw new CayenneRuntimeException("Pagination is not supported with iterator"); + }*/ + if (metadata.getPrefetchTree() != null) { + for (PrefetchTreeNode prefetchTreeNode : metadata.getPrefetchTree().getChildren()) { + if (prefetchTreeNode.isDisjointPrefetch()) { + throw new CayenneRuntimeException("\"Disjoint\" semantic doesn't work with iterator. Use \"Joint\" instead"); + } + if (prefetchTreeNode.isDisjointByIdPrefetch()){ + LOGGER.warn("A separate select query will be created for each iterated item"); + } + } + } + } + private void performIteratedQuery() { Transaction tx = BaseTransaction.getThreadTransaction(); if (tx != null) { @@ -553,14 +561,14 @@ private void runIteratedQuery(Transaction tx){ private void wrapResponseIteratorWithTransactionDecorator(Transaction tx){ ResultIterator iterator = iteratedQueryResponse.currentIterator(); - TransactionResultIteratorDecorator decorator = new TransactionResultIteratorDecorator<>(iterator, tx); + 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) response).setIterator(decorator); + iteratedQueryResponse.setIterator(decorator); } @SuppressWarnings("unchecked") @@ -822,8 +830,7 @@ private PrefetchProcessorNode getPrefetchProcessorNode(List mainRows) { ? metadata.getClassDescriptor() : resultSegment.getClassDescriptor(); - PrefetchProcessorNode node = toResultsTree(descriptor, prefetchTree, mainRows); - return node; + return toResultsTree(descriptor, prefetchTree, mainRows); } } diff --git a/cayenne-server/src/main/java/org/apache/cayenne/util/ObjectContextQueryAction.java b/cayenne-server/src/main/java/org/apache/cayenne/util/ObjectContextQueryAction.java index 7d4c7cc1eb..5487a4b08f 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/util/ObjectContextQueryAction.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/util/ObjectContextQueryAction.java @@ -34,12 +34,7 @@ import org.apache.cayenne.cache.QueryCache; import org.apache.cayenne.cache.QueryCacheEntryFactory; import org.apache.cayenne.map.EntityInheritanceTree; -import org.apache.cayenne.query.EntityResultSegment; -import org.apache.cayenne.query.ObjectIdQuery; -import org.apache.cayenne.query.Query; -import org.apache.cayenne.query.QueryCacheStrategy; -import org.apache.cayenne.query.QueryMetadata; -import org.apache.cayenne.query.RelationshipQuery; +import org.apache.cayenne.query.*; import org.apache.cayenne.reflect.ArcProperty; import org.apache.cayenne.reflect.ClassDescriptor; @@ -87,12 +82,13 @@ public ObjectContextQueryAction(ObjectContext actingContext, * Worker method that performs internal query. */ public QueryResponse execute() { - - if (interceptOIDQuery() != DONE) { - if (interceptRelationshipQuery() != DONE) { - if (interceptRefreshQuery() != DONE) { - if (interceptLocalCache() != DONE) { - executePostCache(); + if (interceptIteratedQuery() != DONE) { + if (interceptOIDQuery() != DONE) { + if (interceptRelationshipQuery() != DONE) { + if (interceptRefreshQuery() != DONE) { + if (interceptLocalCache() != DONE) { + executePostCache(); + } } } } @@ -102,6 +98,14 @@ public QueryResponse execute() { return response; } + private boolean interceptIteratedQuery() { + if (query instanceof IteratedQueryDecorator) { + runQuery(); + return DONE; + } + return !DONE; + } + private void executePostCache() { if (interceptInternalQuery() != DONE) { if (interceptPaginatedQuery() != DONE) { diff --git a/cayenne-server/src/test/java/org/apache/cayenne/query/ObjectSelectIteratedQueryIT.java b/cayenne-server/src/test/java/org/apache/cayenne/query/ObjectSelectIteratedQueryIT.java index b6f5a3db5b..0cd1399a78 100644 --- a/cayenne-server/src/test/java/org/apache/cayenne/query/ObjectSelectIteratedQueryIT.java +++ b/cayenne-server/src/test/java/org/apache/cayenne/query/ObjectSelectIteratedQueryIT.java @@ -32,6 +32,7 @@ import org.apache.cayenne.unit.di.server.ServerCase; import org.apache.cayenne.unit.di.server.UseServerRuntime; import org.junit.Before; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -142,6 +143,17 @@ public void queryPrefetchDisjointWithBatchIterator() { } } + @Ignore + @Test(expected = CayenneRuntimeException.class) + public void queryPaginationWithBatchIterator() { + try (ResultBatchIterator iterator = ObjectSelect + .query(Painting.class) + .pageSize(2) + .batchIterator(context, 3)) { + iterator.next(); + } + } + @Test public void queryPrefetchDisjointByIdWithBIterator() { try (ResultIterator iterator = ObjectSelect From 5a3ac7436f2223277496444404bfc55ea0e8993b Mon Sep 17 00:00:00 2001 From: Ivan Nikitka Date: Mon, 16 Oct 2023 15:12:57 +0300 Subject: [PATCH 09/13] IteratedQuery with pagination exception --- .../org/apache/cayenne/access/DataDomainQueryAction.java | 5 ++--- .../apache/cayenne/query/ObjectSelectIteratedQueryIT.java | 3 --- 2 files changed, 2 insertions(+), 6 deletions(-) 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 6a78a29911..c366aea9ac 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 @@ -159,10 +159,9 @@ private boolean interceptIteratedQuery() { } private void validateIteratedQuery() { - System.out.println("Validate"); - /* if (metadata.getPageSize() != 0){ + if (metadata.getPageSize() > 0){ throw new CayenneRuntimeException("Pagination is not supported with iterator"); - }*/ + } if (metadata.getPrefetchTree() != null) { for (PrefetchTreeNode prefetchTreeNode : metadata.getPrefetchTree().getChildren()) { if (prefetchTreeNode.isDisjointPrefetch()) { diff --git a/cayenne-server/src/test/java/org/apache/cayenne/query/ObjectSelectIteratedQueryIT.java b/cayenne-server/src/test/java/org/apache/cayenne/query/ObjectSelectIteratedQueryIT.java index 0cd1399a78..be378ca4f3 100644 --- a/cayenne-server/src/test/java/org/apache/cayenne/query/ObjectSelectIteratedQueryIT.java +++ b/cayenne-server/src/test/java/org/apache/cayenne/query/ObjectSelectIteratedQueryIT.java @@ -26,16 +26,13 @@ import org.apache.cayenne.di.Inject; import org.apache.cayenne.test.jdbc.DBHelper; import org.apache.cayenne.test.jdbc.TableHelper; -import org.apache.cayenne.testdo.testmap.Artist; import org.apache.cayenne.testdo.testmap.Painting; import org.apache.cayenne.unit.di.server.CayenneProjects; import org.apache.cayenne.unit.di.server.ServerCase; import org.apache.cayenne.unit.di.server.UseServerRuntime; import org.junit.Before; import org.junit.Ignore; -import org.junit.Rule; import org.junit.Test; -import org.junit.rules.ExpectedException; import java.sql.SQLException; import java.sql.Types; From 548e94cd405d0d6d88e650c448aafae88c63d368 Mon Sep 17 00:00:00 2001 From: stariy95 Date: Mon, 6 Nov 2023 16:59:49 +0400 Subject: [PATCH 10/13] CAY-2814 Select query iterator() and batchIterator() methods return incorrect results - fix tests --- .../query/ObjectSelectIteratedQueryIT.java | 29 +++++++++----- .../query/SelectByIdIteratedQueryIT.java | 38 ++++++++----------- 2 files changed, 36 insertions(+), 31 deletions(-) diff --git a/cayenne-server/src/test/java/org/apache/cayenne/query/ObjectSelectIteratedQueryIT.java b/cayenne-server/src/test/java/org/apache/cayenne/query/ObjectSelectIteratedQueryIT.java index be378ca4f3..1f40c02057 100644 --- a/cayenne-server/src/test/java/org/apache/cayenne/query/ObjectSelectIteratedQueryIT.java +++ b/cayenne-server/src/test/java/org/apache/cayenne/query/ObjectSelectIteratedQueryIT.java @@ -86,6 +86,7 @@ public void prefetchWithBatchIterator() { try (ResultBatchIterator iterator = ObjectSelect .query(Painting.class) .prefetch(Painting.TO_ARTIST.joint()) + .orderBy(Painting.PAINTING_ID_PK_PROPERTY.asc()) .batchIterator(context, 10)) { int count = 0; while (iterator.hasNext()) { @@ -97,7 +98,7 @@ public void prefetchWithBatchIterator() { assertEquals("Test1", painting.getToArtist().readPropertyDirectly("artistName")); } } - assertEquals(2,count); + assertEquals(2, count); } } @@ -106,16 +107,17 @@ public void queryPrefetchJointWithIterator() { try (ResultIterator iterator = ObjectSelect .query(Painting.class) .prefetch(Painting.TO_ARTIST.joint()) + .orderBy(Painting.PAINTING_ID_PK_PROPERTY.asc()) .iterator(context)) { int count = 0; while (iterator.hasNextRow()) { count++; Painting painting = iterator.nextRow(); - //noinspection ConstantConditions - assertTrue(painting instanceof Painting); - assertEquals("Test1", painting.getToArtist().readPropertyDirectly("artistName")); + //noinspection ConstantConditions + assertTrue(painting instanceof Painting); + assertEquals("Test1", painting.getToArtist().readPropertyDirectly("artistName")); } - assertEquals(20,count); + assertEquals(20, count); } } @@ -156,6 +158,7 @@ public void queryPrefetchDisjointByIdWithBIterator() { try (ResultIterator iterator = ObjectSelect .query(Painting.class) .prefetch(Painting.TO_ARTIST.disjointById()) + .orderBy(Painting.PAINTING_ID_PK_PROPERTY.asc()) .iterator(context)) { int count = 0; while (iterator.hasNextRow()) { @@ -165,15 +168,16 @@ public void queryPrefetchDisjointByIdWithBIterator() { assertTrue(painting instanceof Painting); assertEquals("Test1", painting.getToArtist().readPropertyDirectly("artistName")); } - assertEquals(20,count); + assertEquals(20, count); } } @Test public void queryPrefetchJointWithBatchIterator() { try (ResultBatchIterator iterator = ObjectSelect - .query(Painting.class,"Painting") + .query(Painting.class, "Painting") .prefetch(Painting.TO_ARTIST.joint()) + .orderBy(Painting.PAINTING_ID_PK_PROPERTY.asc()) .batchIterator(context, 5)) { int count = 0; int paintingCounter = 0; @@ -195,6 +199,7 @@ public void QueryWithIterator() { try (ResultIterator iterator = ObjectSelect .query(Painting.class, "Painting") .prefetch(Painting.TO_ARTIST.joint()) + .orderBy(Painting.PAINTING_ID_PK_PROPERTY.asc()) .iterator(context)) { int count = 0; int paintingCounter = 0; @@ -213,6 +218,7 @@ public void QueryWithIterator() { public void mappingWithBatchIterator() { try (ResultBatchIterator iterator = ObjectSelect .columnQuery(Painting.class, Painting.PAINTING_TITLE, Painting.ESTIMATED_PRICE) + .orderBy(Painting.PAINTING_ID_PK_PROPERTY.asc()) .map(this::toDto) .batchIterator(context, 5)) { int count = 0; @@ -226,7 +232,7 @@ public void mappingWithBatchIterator() { } } assertEquals(5, iterator.getBatchSize()); - assertEquals(4,count); + assertEquals(4, count); } } @@ -234,6 +240,7 @@ public void mappingWithBatchIterator() { public void mappingWithIterator() { try (ResultIterator iterator = ObjectSelect .columnQuery(Painting.class, Painting.PAINTING_TITLE, Painting.ESTIMATED_PRICE) + .orderBy(Painting.PAINTING_ID_PK_PROPERTY.asc()) .map(this::toDto) .iterator(context)) { int count = 0; @@ -252,6 +259,7 @@ public void mappingWithIterator() { public void dataRowQueryWithBatchIterator() { try (ResultBatchIterator iterator = ObjectSelect .dataRowQuery(Painting.class) + .orderBy(Painting.PAINTING_ID_PK_PROPERTY.asc()) .batchIterator(context, 5)) { int count = 0; int paintingCounter = 0; @@ -272,6 +280,7 @@ public void dataRowQueryWithBatchIterator() { public void dataRowQueryWithIterator() { try (ResultIterator iterator = ObjectSelect .dataRowQuery(Painting.class) + .orderBy(Painting.PAINTING_ID_PK_PROPERTY.asc()) .iterator(context)) { int count = 0; int paintingCounter = 0; @@ -290,6 +299,7 @@ public void dataRowQueryWithIterator() { public void dbQueryWithIterator() { try (ResultIterator iterator = ObjectSelect .dbQuery("PAINTING") + .orderBy("db:" + Painting.PAINTING_ID_PK_COLUMN) .iterator(context)) { int count = 0; int paintingCounter = 0; @@ -308,6 +318,7 @@ public void dbQueryWithIterator() { public void dbQueryWithBatchIterator() { try (ResultBatchIterator iterator = ObjectSelect .dbQuery("PAINTING") + .orderBy("db:" + Painting.PAINTING_ID_PK_COLUMN) .batchIterator(context, 5)) { int count = 0; int paintingCounter = 0; @@ -342,7 +353,7 @@ static class DTO { private final Long estimatedPrice; public DTO(Object[] data) { - this.title = "dto_" + (String) data[0]; + this.title = "dto_" + data[0]; this.estimatedPrice = ((Number) data[1]).longValue(); } diff --git a/cayenne-server/src/test/java/org/apache/cayenne/query/SelectByIdIteratedQueryIT.java b/cayenne-server/src/test/java/org/apache/cayenne/query/SelectByIdIteratedQueryIT.java index afa32d1a08..6011f5213b 100644 --- a/cayenne-server/src/test/java/org/apache/cayenne/query/SelectByIdIteratedQueryIT.java +++ b/cayenne-server/src/test/java/org/apache/cayenne/query/SelectByIdIteratedQueryIT.java @@ -18,6 +18,7 @@ ****************************************************************/ package org.apache.cayenne.query; +import org.apache.cayenne.Cayenne; import org.apache.cayenne.DataRow; import org.apache.cayenne.ResultBatchIterator; import org.apache.cayenne.ResultIterator; @@ -38,7 +39,6 @@ import java.util.List; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; @UseServerRuntime(CayenneProjects.TESTMAP_PROJECT) @@ -84,15 +84,14 @@ private void createArtistDataSet() throws SQLException { public void queryWithButchIterator() { try (ResultBatchIterator iterator = SelectById .query(Painting.class, Arrays.asList(1,2,3,4,5,6)) - .batchIterator(context, 3)){ + .batchIterator(context, 4)){ int count = 0; - int paintingCounter = 0; while (iterator.hasNext()) { count++; List paintingList = iterator.next(); for (Painting painting : paintingList) { - paintingCounter++; - assertEquals("painting" + paintingCounter, painting.getPaintingTitle()); + assertEquals("painting" + Cayenne.longPKForObject(painting), + painting.getPaintingTitle()); } } assertEquals(2, count); @@ -105,12 +104,11 @@ public void queryWithIterator() { .query(Painting.class, Arrays.asList(1,2,3,4,5,6)) .iterator(context)){ int count = 0; - int paintingCounter = 0; while (iterator.hasNextRow()) { count++; Painting painting = iterator.nextRow(); - paintingCounter++; - assertEquals("painting" + paintingCounter, painting.getPaintingTitle()); + assertEquals("painting" + Cayenne.longPKForObject(painting), + painting.getPaintingTitle()); } assertEquals(6, count); } @@ -118,18 +116,16 @@ public void queryWithIterator() { @Test public void dataRowQueryWithBatchIterator() { - try (ResultBatchIterator iterator = SelectById + try (ResultBatchIterator iterator = SelectById .dataRowQuery(Painting.class, 1, 2,3,4,5,6) - .batchIterator(context, 3)) { + .batchIterator(context, 4)) { int count = 0; - int paintingCounter = 0; while (iterator.hasNext()) { count++; - List rows = iterator.next(); - for (Object row : rows) { - paintingCounter++; - Painting painting = context.objectFromDataRow(Painting.class, (DataRow) row); - assertEquals("painting" + paintingCounter, painting.getPaintingTitle()); + List rows = iterator.next(); + for (DataRow row : rows) { + assertEquals("painting" + row.get(Painting.PAINTING_ID_PK_COLUMN), + row.get("PAINTING_TITLE")); } } assertEquals(2, count); @@ -138,17 +134,15 @@ public void dataRowQueryWithBatchIterator() { @Test public void dataRowQueryWithIterator() { - try (ResultIterator iterator = SelectById + try (ResultIterator iterator = SelectById .dataRowQuery(Painting.class, 1, 2,3,4,5,6) .iterator(context)) { int count = 0; - int paintingCounter = 0; while (iterator.hasNextRow()) { count++; - paintingCounter++; - Object row = iterator.nextRow(); - Painting painting = context.objectFromDataRow(Painting.class, (DataRow) row); - assertEquals("painting" + paintingCounter, painting.getPaintingTitle()); + DataRow row = iterator.nextRow(); + assertEquals("painting" + row.get(Painting.PAINTING_ID_PK_COLUMN), + row.get("PAINTING_TITLE")); } assertEquals(6, count); } From c0674e21af5ab1173ca3065e2552b61527f78e7d Mon Sep 17 00:00:00 2001 From: stariy95 Date: Mon, 6 Nov 2023 17:10:47 +0400 Subject: [PATCH 11/13] CAY-2814 Select query iterator() and batchIterator() methods return incorrect results - minor code cleanup --- .../main/java/org/apache/cayenne/access/DataContext.java | 5 ++--- .../org/apache/cayenne/access/DataDomainQueryAction.java | 6 ++---- 2 files changed, 4 insertions(+), 7 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 6d974b5a5b..347eaf7bff 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 @@ -797,7 +797,7 @@ GraphDiff flushToParent(boolean cascade) { public ResultIterator iterator(final Select query) { IteratedQueryDecorator queryDecorator = new IteratedQueryDecorator(query); Query queryToRun = nonNullDelegate().willPerformQuery(this, queryDecorator); - QueryResponse queryResponse = (onQuery(this, queryToRun)); + QueryResponse queryResponse = onQuery(this, queryToRun); return (ResultIterator) queryResponse.currentIterator(); } @@ -816,8 +816,7 @@ public ResultIterator iterator(final Select query) { * {@link #iterate(Select, org.apache.cayenne.ResultIteratorCallback)} to * get access to objects. */ - // TODO: deprecate once all selecting queries start implementing Select - // interface + // TODO: deprecate once all selecting queries start implementing Select interface @SuppressWarnings({ "rawtypes" }) public ResultIterator performIteratedQuery(Query query) { 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 c366aea9ac..235f424279 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 @@ -726,10 +726,8 @@ public void nextRows(Query q, ResultIterator it) { @Override public void nextGeneratedRows(Query query, ResultIterator keys, List idsToUpdate) { if (keys != null) { - try { + try (keys) { nextRows(query, keys.allRows()); - } finally { - keys.close(); } } } @@ -979,7 +977,7 @@ Object[] convert(Object[] objectsArray) { private class IdentityConversionStrategy extends ObjectConversionStrategy { @Override - void convert(List mainRows) { + void convert(List mainRows) { //noop } From 84c6af44391c764910e64c9d1bc56630e1053df1 Mon Sep 17 00:00:00 2001 From: stariy95 Date: Mon, 6 Nov 2023 17:16:24 +0400 Subject: [PATCH 12/13] CAY-2814 Select query iterator() and batchIterator() methods return incorrect results - fix tests --- .../cayenne/query/SQLSelectIteratedQueryIT.java | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/cayenne-server/src/test/java/org/apache/cayenne/query/SQLSelectIteratedQueryIT.java b/cayenne-server/src/test/java/org/apache/cayenne/query/SQLSelectIteratedQueryIT.java index f38017fb3c..d2f7f42bd7 100644 --- a/cayenne-server/src/test/java/org/apache/cayenne/query/SQLSelectIteratedQueryIT.java +++ b/cayenne-server/src/test/java/org/apache/cayenne/query/SQLSelectIteratedQueryIT.java @@ -34,8 +34,6 @@ import java.sql.SQLException; import java.sql.Types; -import java.util.Arrays; -import java.util.Collections; import java.util.List; import static org.junit.Assert.assertEquals; @@ -120,7 +118,7 @@ public void scalarQueryWithIterator() { @Test public void dataRowQueryWithBatchIterator() { try (ResultBatchIterator iterator = SQLSelect - .dataRowQuery("SELECT * FROM PAINTING") + .dataRowQuery("SELECT * FROM PAINTING ORDER BY PAINTING_ID") .batchIterator(context, 5)) { int count = 0; int paintingCounter = 0; @@ -140,7 +138,7 @@ public void dataRowQueryWithBatchIterator() { @Test public void dataRowQueryWithIterator() { try (ResultIterator iterator = SQLSelect - .dataRowQuery("SELECT * FROM PAINTING") + .dataRowQuery("SELECT * FROM PAINTING ORDER BY PAINTING_ID") .iterator(context)) { int count = 0; int paintingCounter = 0; @@ -158,7 +156,7 @@ public void dataRowQueryWithIterator() { @Test public void QueryWithBatchIterator() { try (ResultBatchIterator iterator = SQLSelect - .query(Painting.class,"SELECT * FROM PAINTING") + .query(Painting.class,"SELECT * FROM PAINTING ORDER BY PAINTING_ID") .batchIterator(context, 5)) { int count = 0; int paintingCounter = 0; @@ -177,7 +175,7 @@ public void QueryWithBatchIterator() { @Test public void QueryWithIterator() { try (ResultIterator iterator = SQLSelect - .query(Painting.class, "SELECT * FROM PAINTING") + .query(Painting.class, "SELECT * FROM PAINTING ORDER BY PAINTING_ID") .iterator(context)) { int count = 0; int paintingCounter = 0; @@ -196,7 +194,7 @@ public void QueryWithIterator() { @Test public void MappingWithBatchIterator() { try (ResultBatchIterator iterator = SQLSelect - .columnQuery( "SELECT PAINTING_TITLE, ESTIMATED_PRICE FROM PAINTING") + .columnQuery( "SELECT PAINTING_TITLE, ESTIMATED_PRICE FROM PAINTING ORDER BY PAINTING_ID") .map(this::toDto) .batchIterator(context, 5)) { int count = 0; @@ -219,7 +217,7 @@ public void MappingWithBatchIterator() { @Test public void MappingWithIterator() { try (ResultIterator iterator = SQLSelect - .columnQuery("SELECT PAINTING_TITLE, ESTIMATED_PRICE FROM PAINTING") + .columnQuery("SELECT PAINTING_TITLE, ESTIMATED_PRICE FROM PAINTING ORDER BY PAINTING_ID") .map(this::toDto) .iterator(context)) { int count = 0; @@ -245,7 +243,7 @@ static class DTO { private final Long estimatedPrice; public DTO(Object[] data) { - this.title = "dto_" + (String) data[0]; + this.title = "dto_" + data[0]; this.estimatedPrice = ((Number) data[1]).longValue(); } From 78883fc7f1918c0a1b77976031e9203b72d025df Mon Sep 17 00:00:00 2001 From: stariy95 Date: Mon, 6 Nov 2023 17:30:14 +0400 Subject: [PATCH 13/13] CAY-2814 Select query iterator() and batchIterator() methods return incorrect results - fix tests --- .../query/SQLSelectIteratedQueryIT.java | 35 ++++++++++--------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/cayenne-server/src/test/java/org/apache/cayenne/query/SQLSelectIteratedQueryIT.java b/cayenne-server/src/test/java/org/apache/cayenne/query/SQLSelectIteratedQueryIT.java index d2f7f42bd7..09fa51ab37 100644 --- a/cayenne-server/src/test/java/org/apache/cayenne/query/SQLSelectIteratedQueryIT.java +++ b/cayenne-server/src/test/java/org/apache/cayenne/query/SQLSelectIteratedQueryIT.java @@ -117,18 +117,17 @@ public void scalarQueryWithIterator() { @Test public void dataRowQueryWithBatchIterator() { - try (ResultBatchIterator iterator = SQLSelect + try (ResultBatchIterator iterator = SQLSelect .dataRowQuery("SELECT * FROM PAINTING ORDER BY PAINTING_ID") + .upperColumnNames() .batchIterator(context, 5)) { int count = 0; - int paintingCounter = 0; while (iterator.hasNext()) { count++; - List rows = iterator.next(); - for (Object row : rows) { - paintingCounter++; - Painting painting = context.objectFromDataRow(Painting.class, (DataRow) row); - assertEquals("painting" + paintingCounter, painting.getPaintingTitle()); + List rows = iterator.next(); + for (DataRow row : rows) { + assertEquals("painting" + row.get(Painting.PAINTING_ID_PK_COLUMN), + row.get("PAINTING_TITLE")); } } assertEquals(4, count); @@ -137,26 +136,26 @@ public void dataRowQueryWithBatchIterator() { @Test public void dataRowQueryWithIterator() { - try (ResultIterator iterator = SQLSelect + try (ResultIterator iterator = SQLSelect .dataRowQuery("SELECT * FROM PAINTING ORDER BY PAINTING_ID") + .upperColumnNames() .iterator(context)) { int count = 0; - int paintingCounter = 0; while (iterator.hasNextRow()) { count++; - paintingCounter++; - Object row = iterator.nextRow(); - Painting painting = context.objectFromDataRow(Painting.class, (DataRow) row); - assertEquals("painting" + paintingCounter, painting.getPaintingTitle()); + DataRow row = iterator.nextRow(); + assertEquals("painting" + row.get(Painting.PAINTING_ID_PK_COLUMN), + row.get("PAINTING_TITLE")); } assertEquals(20, count); } } @Test - public void QueryWithBatchIterator() { + public void queryWithBatchIterator() { try (ResultBatchIterator iterator = SQLSelect .query(Painting.class,"SELECT * FROM PAINTING ORDER BY PAINTING_ID") + .upperColumnNames() .batchIterator(context, 5)) { int count = 0; int paintingCounter = 0; @@ -173,9 +172,10 @@ public void QueryWithBatchIterator() { } @Test - public void QueryWithIterator() { + public void queryWithIterator() { try (ResultIterator iterator = SQLSelect .query(Painting.class, "SELECT * FROM PAINTING ORDER BY PAINTING_ID") + .upperColumnNames() .iterator(context)) { int count = 0; int paintingCounter = 0; @@ -192,7 +192,7 @@ public void QueryWithIterator() { //MapperConversationStrategy //MixedConversationStrategy @Test - public void MappingWithBatchIterator() { + public void mappingWithBatchIterator() { try (ResultBatchIterator iterator = SQLSelect .columnQuery( "SELECT PAINTING_TITLE, ESTIMATED_PRICE FROM PAINTING ORDER BY PAINTING_ID") .map(this::toDto) @@ -215,9 +215,10 @@ public void MappingWithBatchIterator() { //MapperConversationStrategy //MixedConversationStrategy @Test - public void MappingWithIterator() { + public void mappingWithIterator() { try (ResultIterator iterator = SQLSelect .columnQuery("SELECT PAINTING_TITLE, ESTIMATED_PRICE FROM PAINTING ORDER BY PAINTING_ID") + .upperColumnNames() .map(this::toDto) .iterator(context)) { int count = 0;