This is an automated email from the ASF dual-hosted git repository.
ntimofeev pushed a commit to branch STABLE-4.2
in repository https://gitbox.apache.org/repos/asf/cayenne.git
The following commit(s) were added to refs/heads/STABLE-4.2 by this push:
new c987980c7 CAY-2841 Multi column ColumnSelect with SHARED_CACHE fails
after 1st select
c987980c7 is described below
commit c987980c7229782cd813966d6dfc56a67bccea15
Author: Nikita Timofeev <[email protected]>
AuthorDate: Wed Feb 28 14:33:39 2024 +0400
CAY-2841 Multi column ColumnSelect with SHARED_CACHE fails after 1st select
---
RELEASE-NOTES.txt | 1 +
.../cayenne/access/DataDomainQueryAction.java | 96 +++++++++++++++++-----
.../org/apache/cayenne/query/ColumnSelectIT.java | 19 +++++
3 files changed, 94 insertions(+), 22 deletions(-)
diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt
index 6044843b1..dbad3137f 100644
--- a/RELEASE-NOTES.txt
+++ b/RELEASE-NOTES.txt
@@ -22,6 +22,7 @@ CAY-2813 Regression: Constants.CI_PROPERTY flag is no longer
working for MySQL
CAY-2815 Incorrect translation of aliased expression
CAY-2838 Vertical Inheritance: Problem setting db attribute to null via
flattened path
CAY-2840 Vertical Inheritance: Missing subclass attributes with joint prefetch
+CAY-2841 Multi column ColumnSelect with SHARED_CACHE fails after 1st select
----------------------------------
Release: 4.2
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 f95c2df75..2bf31c551 100644
---
a/cayenne-server/src/main/java/org/apache/cayenne/access/DataDomainQueryAction.java
+++
b/cayenne-server/src/main/java/org/apache/cayenne/access/DataDomainQueryAction.java
@@ -54,6 +54,7 @@ import org.apache.cayenne.util.GenericResponse;
import org.apache.cayenne.util.ListResponse;
import org.apache.cayenne.util.Util;
+import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -89,6 +90,7 @@ class DataDomainQueryAction implements QueryRouter,
OperationObserver {
Map<QueryEngine, Collection<Query>> queriesByNode;
Map<Query, Query> queriesByExecutedQueries;
boolean noObjectConversion;
+ boolean cachedResult;
/*
* A constructor for the "new" way of performing a query via 'execute' with
@@ -412,6 +414,7 @@ class DataDomainQueryAction implements QueryRouter,
OperationObserver {
// cache
queryCache.put(metadata, factory.createObject());
}
+ cachedResult = true;
return DONE;
}
@@ -772,38 +775,71 @@ class DataDomainQueryAction implements QueryRouter,
OperationObserver {
@Override
void convert(List<Object[]> mainRows) {
+ if (mainRows.isEmpty()) {
+ // just a sanity check, should not be a valid case
+ return;
+ }
- int rowsLen = mainRows.size();
+ // do we have anything to convert to objects inside mainRows?
+ boolean needConversion = needConversion();
- List<Object> rsMapping = metadata.getResultSetMapping();
- int width = rsMapping.size();
+ // create result list
+ List<Object[]> result = createResultList(mainRows, needConversion);
// no conversions needed for scalar positions;
- // reuse Object[]'s to fill them with resolved objects
+ if(needConversion) {
+ // reuse Object[]'s to fill them with resolved objects
+ List<PrefetchProcessorNode> segmentNodes =
doInPlaceConversion(result);
+
+ // invoke callbacks now that all objects are resolved...
+ LifecycleCallbackRegistry callbackRegistry =
context.getEntityResolver().getCallbackRegistry();
+ if (!callbackRegistry.isEmpty(LifecycleEvent.POST_LOAD)) {
+ for (PrefetchProcessorNode node : segmentNodes) {
+ performPostLoadCallbacks(node, callbackRegistry);
+ }
+ }
+ }
+
+ // distinct filtering
+ if (!metadata.isSuppressingDistinct()) {
+ Set<List<?>> seen = new HashSet<>(result.size());
+ result.removeIf(objects -> !seen.add(Arrays.asList(objects)));
+ }
+
+ updateResponse(mainRows, result);
+ }
+
+ private List<PrefetchProcessorNode> doInPlaceConversion(List<Object[]>
result) {
+ List<Object> resultSetMapping = metadata.getResultSetMapping();
+ int width = resultSetMapping.size();
+ int height = result.size();
List<PrefetchProcessorNode> segmentNodes = new ArrayList<>(width);
for (int i = 0; i < width; i++) {
- Object mapping = rsMapping.get(i);
+ Object mapping = resultSetMapping.get(i);
if (mapping instanceof EntityResultSegment) {
EntityResultSegment entitySegment = (EntityResultSegment)
mapping;
PrefetchProcessorNode nextResult =
toResultsTree(entitySegment.getClassDescriptor(),
- metadata.getPrefetchTree(), mainRows, i);
+ metadata.getPrefetchTree(), result, i);
segmentNodes.add(nextResult);
List<Persistent> objects = nextResult.getObjects();
-
- for (int j = 0; j < rowsLen; j++) {
- Object[] row = mainRows.get(j);
+ for (int j = 0; j < height; j++) {
+ Object[] row = result.get(j);
row[i] = objects.get(j);
}
} else if (mapping instanceof EmbeddableResultSegment) {
- EmbeddableResultSegment resultSegment =
(EmbeddableResultSegment)mapping;
+ EmbeddableResultSegment resultSegment =
(EmbeddableResultSegment) mapping;
Embeddable embeddable = resultSegment.getEmbeddable();
- Class<?> embeddableClass =
objectFactory.getJavaClass(embeddable.getClassName());
+ @SuppressWarnings("unchecked")
+ Class<? extends EmbeddableObject> embeddableClass =
(Class<? extends EmbeddableObject>) objectFactory
+ .getJavaClass(embeddable.getClassName());
try {
- for(Object[] row : mainRows) {
- DataRow dataRow = (DataRow)row[i];
- EmbeddableObject eo =
(EmbeddableObject)embeddableClass.newInstance();
+ Constructor<? extends EmbeddableObject>
declaredConstructor = embeddableClass
+ .getDeclaredConstructor();
+ for (Object[] row : result) {
+ DataRow dataRow = (DataRow) row[i];
+ EmbeddableObject eo =
declaredConstructor.newInstance();
dataRow.forEach(eo::writePropertyDirectly);
row[i] = eo;
}
@@ -812,20 +848,36 @@ class DataDomainQueryAction implements QueryRouter,
OperationObserver {
}
}
}
+ return segmentNodes;
+ }
- if(!metadata.isSuppressingDistinct()) {
- Set<List<?>> seen = new HashSet<>(mainRows.size());
- mainRows.removeIf(objects ->
!seen.add(Arrays.asList(objects)));
+ private List<Object[]> createResultList(List<Object[]> mainRows,
boolean needConversion) {
+ if(!cachedResult) {
+ // fast-path, we can reuse existing rows
+ return mainRows;
}
- // invoke callbacks now that all objects are resolved...
- LifecycleCallbackRegistry callbackRegistry =
context.getEntityResolver().getCallbackRegistry();
+ if(!needConversion) {
+ // no conversion needed, so can clone only top-level list
+ return new ArrayList<>(mainRows);
+ }
- if (!callbackRegistry.isEmpty(LifecycleEvent.POST_LOAD)) {
- for (PrefetchProcessorNode node : segmentNodes) {
- performPostLoadCallbacks(node, callbackRegistry);
+ // slowest path, deep copy everything
+ List<Object[]> result = new ArrayList<>(mainRows.size());
+ for(Object[] row : mainRows) {
+ result.add(Arrays.copyOf(row,
metadata.getResultSetMapping().size()));
+ }
+ return result;
+ }
+
+ private boolean needConversion() {
+ for (Object mapping : metadata.getResultSetMapping()) {
+ if (mapping instanceof EntityResultSegment
+ || mapping instanceof EmbeddableResultSegment) {
+ return true;
}
}
+ return false;
}
}
diff --git
a/cayenne-server/src/test/java/org/apache/cayenne/query/ColumnSelectIT.java
b/cayenne-server/src/test/java/org/apache/cayenne/query/ColumnSelectIT.java
index f9496e020..21430594c 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/query/ColumnSelectIT.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/query/ColumnSelectIT.java
@@ -59,6 +59,7 @@ import org.junit.Ignore;
import org.junit.Test;
import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.*;
/**
@@ -1195,6 +1196,24 @@ public class ColumnSelectIT extends ServerCase {
}
+ @Test
+ public void testSharedCache() {
+ ColumnSelect<Object[]> query = ObjectSelect.query(Artist.class)
+ .columns(Artist.ARTIST_NAME, Artist.DATE_OF_BIRTH,
PropertyFactory.createSelf(Artist.class))
+ .orderBy(Artist.ARTIST_ID_PK_PROPERTY.asc())
+ .cacheStrategy(QueryCacheStrategy.SHARED_CACHE);
+
+ List<Object[]> result = query.select(context);
+ assertEquals(20, result.size());
+ assertThat("Should be an instance of Artist",
+ instanceOf(Artist.class).matches(result.get(0)[2]));
+
+ List<Object[]> result2 = query.select(context);
+ assertEquals(20, result2.size());
+ assertThat("Should be an instance of Artist",
+ instanceOf(Artist.class).matches(result.get(0)[2]));
+ }
+
static class TestPojo {
String name;
Date date;