You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cayenne.apache.org by nt...@apache.org on 2017/08/16 09:48:09 UTC
cayenne git commit: CAY-2349 Cache issue: 'SelectQuery' with
prefetches loses relationships
Repository: cayenne
Updated Branches:
refs/heads/STABLE-4.0 a3e5f1412 -> 3ed2301bf
CAY-2349 Cache issue: 'SelectQuery' with prefetches loses relationships
Project: http://git-wip-us.apache.org/repos/asf/cayenne/repo
Commit: http://git-wip-us.apache.org/repos/asf/cayenne/commit/3ed2301b
Tree: http://git-wip-us.apache.org/repos/asf/cayenne/tree/3ed2301b
Diff: http://git-wip-us.apache.org/repos/asf/cayenne/diff/3ed2301b
Branch: refs/heads/STABLE-4.0
Commit: 3ed2301bf0deee0c03179586d271254872994173
Parents: a3e5f14
Author: Nikita Timofeev <st...@gmail.com>
Authored: Wed Aug 16 12:47:20 2017 +0300
Committer: Nikita Timofeev <st...@gmail.com>
Committed: Wed Aug 16 12:47:20 2017 +0300
----------------------------------------------------------------------
.../cayenne/query/SelectQueryMetadata.java | 56 ++++++++-
.../cayenne/access/DataContextPrefetchIT.java | 126 +++++++++++++++++--
.../query/SelectQueryMetadataCacheKeyTest.java | 123 ++++++++++++++++++
docs/doc/src/main/resources/RELEASE-NOTES.txt | 1 +
4 files changed, 293 insertions(+), 13 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/cayenne/blob/3ed2301b/cayenne-server/src/main/java/org/apache/cayenne/query/SelectQueryMetadata.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/query/SelectQueryMetadata.java b/cayenne-server/src/main/java/org/apache/cayenne/query/SelectQueryMetadata.java
index 1c5b04e..37a0301 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/query/SelectQueryMetadata.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/query/SelectQueryMetadata.java
@@ -107,10 +107,9 @@ class SelectQueryMetadata extends BaseQueryMetadata {
}
if(query.getColumns() != null && !query.getColumns().isEmpty()) {
- key.append("/");
traversalHandler = new ToCacheKeyTraversalHandler(resolver.getValueObjectTypeRegistry(), key);
for(Property<?> property : query.getColumns()) {
- key.append("c:");
+ key.append("/c:");
property.getExpression().traverse(traversalHandler);
}
}
@@ -146,6 +145,11 @@ class SelectQueryMetadata extends BaseQueryMetadata {
}
}
+ // add prefetch to cache key per CAY-2349
+ if(query.getPrefetchTree() != null) {
+ query.getPrefetchTree().traverse(new ToCacheKeyPrefetchProcessor(key));
+ }
+
return key.toString();
}
@@ -474,5 +478,51 @@ class SelectQueryMetadata extends BaseQueryMetadata {
}
}
}
- };
+ }
+
+ /**
+ * Prefetch processor that append prefetch tree into cache key.
+ * @since 4.0
+ */
+ static class ToCacheKeyPrefetchProcessor implements PrefetchProcessor {
+
+ StringBuilder out;
+
+ ToCacheKeyPrefetchProcessor(StringBuilder out) {
+ this.out = out;
+ }
+
+ @Override
+ public boolean startPhantomPrefetch(PrefetchTreeNode node) {
+ return true;
+ }
+
+ @Override
+ public boolean startDisjointPrefetch(PrefetchTreeNode node) {
+ out.append("/pd:").append(node.getPath());
+ return true;
+ }
+
+ @Override
+ public boolean startDisjointByIdPrefetch(PrefetchTreeNode node) {
+ out.append("/pi:").append(node.getPath());
+ return true;
+ }
+
+ @Override
+ public boolean startJointPrefetch(PrefetchTreeNode node) {
+ out.append("/pj:").append(node.getPath());
+ return true;
+ }
+
+ @Override
+ public boolean startUnknownPrefetch(PrefetchTreeNode node) {
+ out.append("/pu:").append(node.getPath());
+ return true;
+ }
+
+ @Override
+ public void finishPrefetch(PrefetchTreeNode node) {
+ }
+ }
}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/3ed2301b/cayenne-server/src/test/java/org/apache/cayenne/access/DataContextPrefetchIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/DataContextPrefetchIT.java b/cayenne-server/src/test/java/org/apache/cayenne/access/DataContextPrefetchIT.java
index 792d815..dce94a4 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/access/DataContextPrefetchIT.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/access/DataContextPrefetchIT.java
@@ -20,6 +20,7 @@
package org.apache.cayenne.access;
import org.apache.cayenne.Cayenne;
+import org.apache.cayenne.Fault;
import org.apache.cayenne.PersistenceState;
import org.apache.cayenne.ValueHolder;
import org.apache.cayenne.di.Inject;
@@ -28,6 +29,7 @@ import org.apache.cayenne.exp.ExpressionFactory;
import org.apache.cayenne.exp.Property;
import org.apache.cayenne.map.ObjEntity;
import org.apache.cayenne.map.ObjRelationship;
+import org.apache.cayenne.query.ObjectSelect;
import org.apache.cayenne.query.QueryCacheStrategy;
import org.apache.cayenne.query.SelectQuery;
import org.apache.cayenne.test.jdbc.DBHelper;
@@ -35,6 +37,7 @@ import org.apache.cayenne.test.jdbc.TableHelper;
import org.apache.cayenne.testdo.testmap.ArtGroup;
import org.apache.cayenne.testdo.testmap.Artist;
import org.apache.cayenne.testdo.testmap.ArtistExhibit;
+import org.apache.cayenne.testdo.testmap.Gallery;
import org.apache.cayenne.testdo.testmap.Painting;
import org.apache.cayenne.testdo.testmap.PaintingInfo;
import org.apache.cayenne.unit.di.DataChannelInterceptor;
@@ -81,8 +84,8 @@ public class DataContextPrefetchIT extends ServerCase {
tArtist.setColumns("ARTIST_ID", "ARTIST_NAME");
tPainting = new TableHelper(dbHelper, "PAINTING");
- tPainting.setColumns("PAINTING_ID", "PAINTING_TITLE", "ARTIST_ID", "ESTIMATED_PRICE").setColumnTypes(
- Types.INTEGER, Types.VARCHAR, Types.BIGINT, Types.DECIMAL);
+ tPainting.setColumns("PAINTING_ID", "PAINTING_TITLE", "ARTIST_ID", "ESTIMATED_PRICE", "GALLERY_ID").setColumnTypes(
+ Types.INTEGER, Types.VARCHAR, Types.BIGINT, Types.DECIMAL, Types.INTEGER);
tPaintingInfo = new TableHelper(dbHelper, "PAINTING_INFO");
tPaintingInfo.setColumns("PAINTING_ID", "TEXT_REVIEW");
@@ -106,15 +109,15 @@ public class DataContextPrefetchIT extends ServerCase {
protected void createTwoArtistsAndTwoPaintingsDataSet() throws Exception {
tArtist.insert(11, "artist2");
tArtist.insert(101, "artist3");
- tPainting.insert(6, "p_artist3", 101, 1000);
- tPainting.insert(7, "p_artist2", 11, 2000);
+ tPainting.insert(6, "p_artist3", 101, 1000, null);
+ tPainting.insert(7, "p_artist2", 11, 2000, null);
}
protected void createArtistWithTwoPaintingsAndTwoInfosDataSet() throws Exception {
tArtist.insert(11, "artist2");
- tPainting.insert(6, "p_artist2", 11, 1000);
- tPainting.insert(7, "p_artist3", 11, 2000);
+ tPainting.insert(6, "p_artist2", 11, 1000, null);
+ tPainting.insert(7, "p_artist3", 11, 2000, null);
tPaintingInfo.insert(6, "xYs");
}
@@ -545,9 +548,9 @@ public class DataContextPrefetchIT extends ServerCase {
tArtGroup.insert(1, "AG");
tArtist.insert(11, "artist2");
tArtist.insert(101, "artist3");
- tPainting.insert(6, "p_artist3", 101, 1000);
- tPainting.insert(7, "p_artist21", 11, 2000);
- tPainting.insert(8, "p_artist22", 11, 3000);
+ tPainting.insert(6, "p_artist3", 101, 1000, null);
+ tPainting.insert(7, "p_artist21", 11, 2000, null);
+ tPainting.insert(8, "p_artist22", 11, 3000, null);
// flattened join matches an object that is NOT the one we are looking
// for
@@ -657,7 +660,7 @@ public class DataContextPrefetchIT extends ServerCase {
@Test
public void testPrefetchingToOneNull() throws Exception {
- tPainting.insert(6, "p_Xty", null, 1000);
+ tPainting.insert(6, "p_Xty", null, 1000, null);
SelectQuery q = new SelectQuery(Painting.class);
q.addPrefetch(Painting.TO_ARTIST.disjoint());
@@ -842,4 +845,107 @@ public class DataContextPrefetchIT extends ServerCase {
});
}
+ /**
+ * This test and next one is the result of CAY-2349 fix
+ */
+ @Test
+ public void testPrefetchWithLocalCache() throws Exception {
+ tArtist.deleteAll();
+ tGallery.deleteAll();
+ tPainting.deleteAll();
+ tArtist.insert(1, "artist1");
+ tGallery.insert(1, "gallery1");
+ tPainting.insert(1, "painting1", 1, 100, 1);
+
+ List<Painting> paintings = ObjectSelect.query(Painting.class)
+ .localCache("g1").select(context);
+ assertEquals(1, paintings.size());
+ assertTrue(paintings.get(0).readPropertyDirectly(Painting.TO_ARTIST.getName()) instanceof Fault);
+
+ paintings = ObjectSelect.query(Painting.class)
+ .prefetch(Painting.TO_ARTIST.joint())
+ .localCache("g1").select(context);
+ assertEquals(1, paintings.size());
+ assertTrue(paintings.get(0).readPropertyDirectly(Painting.TO_ARTIST.getName()) instanceof Artist);
+
+ queryInterceptor.runWithQueriesBlocked(new UnitTestClosure() {
+
+ public void execute() {
+ List<Painting> paintings = ObjectSelect.query(Painting.class)
+ .prefetch(Painting.TO_ARTIST.joint())
+ .localCache("g1").select(context);
+ assertEquals(1, paintings.size());
+ assertTrue(paintings.get(0).readPropertyDirectly(Painting.TO_ARTIST.getName()) instanceof Artist);
+ }
+ });
+ }
+
+ @Test
+ public void testPrefetchWithSharedCache() throws Exception {
+ tArtist.deleteAll();
+ tGallery.deleteAll();
+ tPainting.deleteAll();
+ tArtist.insert(1, "artist1");
+ tGallery.insert(1, "gallery1");
+ tPainting.insert(1, "painting1", 1, 100, 1);
+
+ final ObjectSelect<Painting> s1 = ObjectSelect.query(Painting.class)
+ .sharedCache("g1");
+
+ final ObjectSelect<Painting> s2 = ObjectSelect.query(Painting.class)
+ .prefetch(Painting.TO_ARTIST.disjoint())
+ .sharedCache("g1");
+
+ final ObjectSelect<Painting> s3 = ObjectSelect.query(Painting.class)
+ .prefetch(Painting.TO_GALLERY.joint())
+ .sharedCache("g1");
+
+ final ObjectSelect<Painting> s4 = ObjectSelect.query(Painting.class)
+ .prefetch(Painting.TO_ARTIST.disjoint())
+ .prefetch(Painting.TO_GALLERY.joint())
+ .sharedCache("g1");
+
+ // first iteration select from DB and cache
+ List<Painting> paintings = s1.select(context);
+ assertEquals(1, paintings.size());
+ assertTrue(paintings.get(0).readPropertyDirectly(Painting.TO_ARTIST.getName()) instanceof Fault);
+ assertTrue(paintings.get(0).readPropertyDirectly(Painting.TO_GALLERY.getName()) instanceof Fault);
+
+ paintings = s2.select(context);
+ assertEquals(1, paintings.size());
+ assertTrue(paintings.get(0).readPropertyDirectly(Painting.TO_ARTIST.getName()) instanceof Artist);
+ assertTrue(paintings.get(0).readPropertyDirectly(Painting.TO_GALLERY.getName()) instanceof Fault);
+
+ paintings = s3.select(context);
+ assertEquals(1, paintings.size());
+ assertTrue(paintings.get(0).readPropertyDirectly(Painting.TO_ARTIST.getName()) instanceof Fault);
+ assertTrue(paintings.get(0).readPropertyDirectly(Painting.TO_GALLERY.getName()) instanceof Gallery);
+
+ paintings = s4.select(context);
+ assertEquals(1, paintings.size());
+ assertTrue(paintings.get(0).readPropertyDirectly(Painting.TO_ARTIST.getName()) instanceof Artist);
+ assertTrue(paintings.get(0).readPropertyDirectly(Painting.TO_GALLERY.getName()) instanceof Gallery);
+
+ queryInterceptor.runWithQueriesBlocked(new UnitTestClosure() {
+
+ public void execute() {
+ // select from cache
+ List<Painting> paintings = s2.select(context);
+ assertEquals(1, paintings.size());
+ assertTrue(paintings.get(0).readPropertyDirectly(Painting.TO_ARTIST.getName()) instanceof Artist);
+ assertTrue(paintings.get(0).readPropertyDirectly(Painting.TO_GALLERY.getName()) instanceof Fault);
+
+ paintings = s3.select(context);
+ assertEquals(1, paintings.size());
+ assertTrue(paintings.get(0).readPropertyDirectly(Painting.TO_ARTIST.getName()) instanceof Fault);
+ assertTrue(paintings.get(0).readPropertyDirectly(Painting.TO_GALLERY.getName()) instanceof Gallery);
+
+ paintings = s4.select(context);
+ assertEquals(1, paintings.size());
+ assertTrue(paintings.get(0).readPropertyDirectly(Painting.TO_ARTIST.getName()) instanceof Artist);
+ assertTrue(paintings.get(0).readPropertyDirectly(Painting.TO_GALLERY.getName()) instanceof Gallery);
+ }
+ });
+ }
+
}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/3ed2301b/cayenne-server/src/test/java/org/apache/cayenne/query/SelectQueryMetadataCacheKeyTest.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/query/SelectQueryMetadataCacheKeyTest.java b/cayenne-server/src/test/java/org/apache/cayenne/query/SelectQueryMetadataCacheKeyTest.java
index efd03c4..dfbb23e 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/query/SelectQueryMetadataCacheKeyTest.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/query/SelectQueryMetadataCacheKeyTest.java
@@ -25,6 +25,8 @@ import org.apache.cayenne.access.types.ValueObjectType;
import org.apache.cayenne.access.types.ValueObjectTypeRegistry;
import org.apache.cayenne.exp.ExpressionFactory;
import org.apache.cayenne.exp.TraversalHandler;
+import org.apache.cayenne.testdo.testmap.Artist;
+import org.apache.cayenne.testdo.testmap.Painting;
import org.junit.Before;
import org.junit.Test;
@@ -209,10 +211,131 @@ public class SelectQueryMetadataCacheKeyTest {
assertNotEquals(s6, s7);
}
+ @Test
+ public void testPrefetchEmpty() {
+ PrefetchTreeNode prefetchTreeNode = new PrefetchTreeNode();
+ prefetchTreeNode.traverse(newPrefetchProcessor());
+ assertTrue(cacheKey.toString().isEmpty());
+ }
+
+ @Test
+ public void testPrefetchSingle() {
+ PrefetchTreeNode prefetchTreeNode;
+
+ prefetchTreeNode = new PrefetchTreeNode();
+ prefetchTreeNode.merge(Artist.PAINTING_ARRAY.joint());
+ prefetchTreeNode.traverse(newPrefetchProcessor());
+ String s1 = cacheKey.toString();
+
+ prefetchTreeNode = new PrefetchTreeNode();
+ prefetchTreeNode.merge(Artist.PAINTING_ARRAY.joint());
+ prefetchTreeNode.traverse(newPrefetchProcessor());
+ String s2 = cacheKey.toString();
+
+ assertFalse(s1.isEmpty());
+ assertEquals(s1, s2);
+ }
+
+ @Test
+ public void testPrefetchSemantics() {
+ PrefetchTreeNode prefetchTreeNode;
+
+ prefetchTreeNode = new PrefetchTreeNode();
+ prefetchTreeNode.merge(Artist.PAINTING_ARRAY.joint());
+ prefetchTreeNode.traverse(newPrefetchProcessor());
+ String s1 = cacheKey.toString();
+
+ prefetchTreeNode = new PrefetchTreeNode();
+ prefetchTreeNode.merge(Artist.PAINTING_ARRAY.disjoint());
+ prefetchTreeNode.traverse(newPrefetchProcessor());
+ String s2 = cacheKey.toString();
+
+ prefetchTreeNode = new PrefetchTreeNode();
+ prefetchTreeNode.merge(Artist.PAINTING_ARRAY.disjointById());
+ prefetchTreeNode.traverse(newPrefetchProcessor());
+ String s3 = cacheKey.toString();
+
+ prefetchTreeNode = new PrefetchTreeNode();
+ prefetchTreeNode.merge(Artist.PAINTING_ARRAY.disjoint());
+ prefetchTreeNode.traverse(newPrefetchProcessor());
+ String s4 = cacheKey.toString();
+
+ assertNotEquals(s1, s2);
+ assertNotEquals(s2, s3);
+ assertEquals(s2, s4);
+ }
+
+ @Test
+ public void testPrefetchMultiNodes() {
+ PrefetchTreeNode prefetchTreeNode;
+
+ prefetchTreeNode = new PrefetchTreeNode();
+ prefetchTreeNode.merge(Artist.PAINTING_ARRAY.joint());
+ prefetchTreeNode.traverse(newPrefetchProcessor());
+ String s1 = cacheKey.toString();
+
+ prefetchTreeNode = new PrefetchTreeNode();
+ prefetchTreeNode.merge(Artist.PAINTING_ARRAY.joint());
+ prefetchTreeNode.merge(Artist.GROUP_ARRAY.joint());
+ prefetchTreeNode.traverse(newPrefetchProcessor());
+ String s2 = cacheKey.toString();
+
+ prefetchTreeNode = new PrefetchTreeNode();
+ prefetchTreeNode.merge(Artist.PAINTING_ARRAY.joint());
+ prefetchTreeNode.merge(Artist.GROUP_ARRAY.joint());
+ prefetchTreeNode.merge(Artist.ARTIST_EXHIBIT_ARRAY.joint());
+ prefetchTreeNode.traverse(newPrefetchProcessor());
+ String s3 = cacheKey.toString();
+
+ prefetchTreeNode = new PrefetchTreeNode();
+ prefetchTreeNode.merge(Artist.PAINTING_ARRAY.joint());
+ prefetchTreeNode.merge(Artist.GROUP_ARRAY.joint());
+ prefetchTreeNode.merge(Artist.ARTIST_EXHIBIT_ARRAY.joint());
+ prefetchTreeNode.traverse(newPrefetchProcessor());
+ String s4 = cacheKey.toString();
+
+ assertNotEquals(s1, s2);
+ assertNotEquals(s2, s3);
+ assertEquals(s3, s4);
+ }
+
+ @Test
+ public void testPrefetchLongPaths() {
+ PrefetchTreeNode prefetchTreeNode;
+
+ prefetchTreeNode = new PrefetchTreeNode();
+ prefetchTreeNode.merge(Artist.PAINTING_ARRAY.joint());
+ prefetchTreeNode.traverse(newPrefetchProcessor());
+ String s1 = cacheKey.toString();
+
+ prefetchTreeNode = new PrefetchTreeNode();
+ prefetchTreeNode.merge(Artist.PAINTING_ARRAY.dot(Painting.TO_ARTIST).joint());
+ prefetchTreeNode.traverse(newPrefetchProcessor());
+ String s2 = cacheKey.toString();
+
+ prefetchTreeNode = new PrefetchTreeNode();
+ prefetchTreeNode.merge(Artist.PAINTING_ARRAY.dot(Painting.TO_ARTIST).dot(Artist.GROUP_ARRAY).joint());
+ prefetchTreeNode.traverse(newPrefetchProcessor());
+ String s3 = cacheKey.toString();
+
+ prefetchTreeNode = new PrefetchTreeNode();
+ prefetchTreeNode.merge(Artist.PAINTING_ARRAY.dot(Painting.TO_ARTIST).dot(Artist.GROUP_ARRAY).joint());
+ prefetchTreeNode.traverse(newPrefetchProcessor());
+ String s4 = cacheKey.toString();
+
+ assertNotEquals(s1, s2);
+ assertNotEquals(s2, s3);
+ assertEquals(s3, s4);
+ }
+
private TraversalHandler newHandler() {
return new SelectQueryMetadata.ToCacheKeyTraversalHandler(registry, cacheKey = new StringBuilder());
}
+ private PrefetchProcessor newPrefetchProcessor() {
+ return new SelectQueryMetadata.ToCacheKeyPrefetchProcessor(cacheKey = new StringBuilder());
+ }
+
/* ************* Test types *************** */
/**
http://git-wip-us.apache.org/repos/asf/cayenne/blob/3ed2301b/docs/doc/src/main/resources/RELEASE-NOTES.txt
----------------------------------------------------------------------
diff --git a/docs/doc/src/main/resources/RELEASE-NOTES.txt b/docs/doc/src/main/resources/RELEASE-NOTES.txt
index 544a276..a8d263f 100644
--- a/docs/doc/src/main/resources/RELEASE-NOTES.txt
+++ b/docs/doc/src/main/resources/RELEASE-NOTES.txt
@@ -17,6 +17,7 @@ Bug Fixes:
CAY-2319 Modeler: Embeddable > Attributes. Undo does not cancel pasted objects
CAY-2323 Modeler: Graph. No warning while saving the image with existing name
CAY-2331 cgen: broken templates for data map
+CAY-2349 Cache issue: 'SelectQuery' with prefetches loses relationships
----------------------------------
Release: 4.0.B1