Skip to content

Commit 470f960

Browse files
committed
CAY-2906 In-memory evaluation of (not) exists expressions
1 parent 9782dad commit 470f960

3 files changed

Lines changed: 172 additions & 5 deletions

File tree

cayenne/src/main/java/org/apache/cayenne/exp/parser/ASTExists.java

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,14 +46,20 @@ protected int getRequiredChildrenCount() {
4646
}
4747

4848
@Override
49-
protected Boolean evaluateSubNode(Object o, Object[] evaluatedChildren) throws Exception {
50-
if(evaluatedChildren.length == 0) {
49+
protected Object evaluateNode(Object o) throws Exception {
50+
if (jjtGetNumChildren() != 1) {
5151
return Boolean.FALSE;
5252
}
53-
return notEmpty(evaluatedChildren[0]);
53+
Object firstChild = evaluateChild(0, o);
54+
return evaluateSubNode(firstChild, null);
55+
}
56+
57+
@Override
58+
protected Boolean evaluateSubNode(Object o, Object[] evaluatedChildren) throws Exception {
59+
return notEmpty(o);
5460
}
5561

56-
private Boolean notEmpty(Object child) {
62+
static Boolean notEmpty(Object child) {
5763
if(child instanceof Boolean) {
5864
return (Boolean)child;
5965
}

cayenne/src/main/java/org/apache/cayenne/exp/parser/ASTNotExists.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,18 @@ protected int getRequiredChildrenCount() {
4141
return 1;
4242
}
4343

44+
@Override
45+
protected Object evaluateNode(Object o) throws Exception {
46+
if (jjtGetNumChildren() != 1) {
47+
return Boolean.FALSE;
48+
}
49+
Object firstChild = evaluateChild(0, o);
50+
return evaluateSubNode(firstChild, null);
51+
}
52+
4453
@Override
4554
protected Boolean evaluateSubNode(Object o, Object[] evaluatedChildren) throws Exception {
46-
return null;
55+
return !ASTExists.notEmpty(o);
4756
}
4857

4958
@Override
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
/*****************************************************************
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* https://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
****************************************************************/
19+
20+
package org.apache.cayenne.exp.parser;
21+
22+
import org.apache.cayenne.ObjectContext;
23+
import org.apache.cayenne.di.Inject;
24+
import org.apache.cayenne.exp.Expression;
25+
import org.apache.cayenne.exp.ExpressionException;
26+
import org.apache.cayenne.exp.ExpressionFactory;
27+
import org.apache.cayenne.query.ObjectSelect;
28+
import org.apache.cayenne.test.jdbc.DBHelper;
29+
import org.apache.cayenne.test.jdbc.TableHelper;
30+
import org.apache.cayenne.testdo.testmap.Artist;
31+
import org.apache.cayenne.testdo.testmap.Gallery;
32+
import org.apache.cayenne.testdo.testmap.Painting;
33+
import org.apache.cayenne.unit.di.DataChannelInterceptor;
34+
import org.apache.cayenne.unit.di.runtime.CayenneProjects;
35+
import org.apache.cayenne.unit.di.runtime.RuntimeCase;
36+
import org.apache.cayenne.unit.di.runtime.UseCayenneRuntime;
37+
import org.junit.Before;
38+
import org.junit.Test;
39+
40+
import java.util.List;
41+
42+
import static org.junit.Assert.assertEquals;
43+
44+
@UseCayenneRuntime(CayenneProjects.TESTMAP_PROJECT)
45+
public class ASTNotExistsIT extends RuntimeCase {
46+
47+
@Inject
48+
private ObjectContext context;
49+
50+
@Inject
51+
private DBHelper dbHelper;
52+
53+
@Inject
54+
private DataChannelInterceptor queryInterceptor;
55+
56+
@Before
57+
public void createArtistsDataSet() throws Exception {
58+
TableHelper tArtist = new TableHelper(dbHelper, "ARTIST");
59+
tArtist.setColumns("ARTIST_ID", "ARTIST_NAME", "DATE_OF_BIRTH");
60+
61+
long dateBase = System.currentTimeMillis();
62+
for (int i = 1; i <= 20; i++) {
63+
tArtist.insert(i, "artist" + i, new java.sql.Date(dateBase + 10000 * i));
64+
}
65+
66+
TableHelper tGallery = new TableHelper(dbHelper, "GALLERY");
67+
tGallery.setColumns("GALLERY_ID", "GALLERY_NAME");
68+
tGallery.insert(1, "tate modern");
69+
70+
TableHelper tPaintings = new TableHelper(dbHelper, "PAINTING");
71+
tPaintings.setColumns("PAINTING_ID", "PAINTING_TITLE", "ARTIST_ID", "GALLERY_ID");
72+
for (int i = 1; i <= 20; i++) {
73+
tPaintings.insert(i, "painting" + i, i % 5 + 1, 1);
74+
}
75+
}
76+
77+
@Test(expected = ExpressionException.class)
78+
public void testEvaluateInMemoryNotExistsSubquery() {
79+
ObjectSelect<Painting> subQuery = ObjectSelect.query(Painting.class)
80+
.where(Painting.TO_ARTIST.eq(Artist.ARTIST_ID_PK_PROPERTY.enclosing()));
81+
82+
doEvaluateWithQuery(ExpressionFactory.notExists(subQuery));
83+
}
84+
85+
@Test
86+
public void testEvaluateInMemoryNotExistsExpression() {
87+
// doEvaluateNoQuery(Artist.PAINTING_ARRAY.notExists());
88+
89+
doEvaluateNoQuery(Artist.ARTIST_ID_PK_PROPERTY.eq(6L).andExp(Artist.PAINTING_ARRAY.notExists()));
90+
91+
doEvaluateNoQuery(Artist.PAINTING_ARRAY.dot(Painting.PAINTING_TITLE).like("p%").notExists());
92+
93+
doEvaluateNoQuery(Artist.PAINTING_ARRAY.dot(Painting.PAINTING_TITLE).like("not_exists%").notExists());
94+
95+
doEvaluateNoQuery(Artist.PAINTING_ARRAY.dot(Painting.TO_PAINTING_INFO).notExists());
96+
97+
doEvaluateNoQuery(Artist.PAINTING_ARRAY.dot(Painting.TO_GALLERY).notExists());
98+
99+
doEvaluateNoQuery(Artist.PAINTING_ARRAY.dot(Painting.TO_GALLERY).dot(Gallery.GALLERY_NAME).like("g%").notExists());
100+
101+
doEvaluateNoQuery(Artist.PAINTING_ARRAY.dot(Painting.TO_GALLERY).dot(Gallery.GALLERY_NAME).like("not_exists%").notExists());
102+
103+
doEvaluateNoQuery(Artist.PAINTING_ARRAY.dot(Painting.TO_GALLERY).dot(Gallery.GALLERY_NAME).like("g%")
104+
.andExp(Artist.PAINTING_ARRAY.dot(Painting.PAINTING_TITLE).like("p%"))
105+
.notExists());
106+
107+
doEvaluateNoQuery(Artist.PAINTING_ARRAY.dot(Painting.TO_GALLERY).dot(Gallery.GALLERY_NAME).like("not_exists%")
108+
.andExp(Artist.PAINTING_ARRAY.dot(Painting.PAINTING_TITLE).like("p%"))
109+
.notExists());
110+
111+
doEvaluateNoQuery(Artist.PAINTING_ARRAY.dot(Painting.TO_GALLERY).dot(Gallery.GALLERY_NAME).like("g%")
112+
.andExp(Artist.PAINTING_ARRAY.dot(Painting.PAINTING_TITLE).like("not_exists%"))
113+
.notExists());
114+
115+
doEvaluateNoQuery(Artist.PAINTING_ARRAY.dot(Painting.TO_GALLERY).dot(Gallery.GALLERY_NAME).like("not_exists%")
116+
.andExp(Artist.PAINTING_ARRAY.dot(Painting.PAINTING_TITLE).like("not_exists%"))
117+
.notExists());
118+
119+
}
120+
121+
private void doEvaluateNoQuery(Expression exp) {
122+
List<Artist> artistSelected = ObjectSelect.query(Artist.class, exp)
123+
.orderBy(Artist.ARTIST_ID_PK_PROPERTY.asc())
124+
.select(context);
125+
126+
List<Artist> artists = ObjectSelect.query(Artist.class)
127+
.prefetch(Artist.PAINTING_ARRAY.outer().disjoint())
128+
.prefetch(Artist.PAINTING_ARRAY.outer().dot(Painting.TO_PAINTING_INFO).disjoint())
129+
.prefetch(Artist.PAINTING_ARRAY.outer().dot(Painting.TO_GALLERY).disjoint())
130+
.orderBy(Artist.ARTIST_ID_PK_PROPERTY.asc())
131+
.select(context);
132+
133+
queryInterceptor.runWithQueriesBlocked(() -> {
134+
List<Artist> artistsFiltered = exp.filterObjects(artists);
135+
assertEquals(exp.toString(), artistSelected, artistsFiltered);
136+
});
137+
}
138+
139+
private void doEvaluateWithQuery(Expression exp) {
140+
List<Artist> artistSelected = ObjectSelect.query(Artist.class, exp).select(context);
141+
142+
List<Artist> artists = ObjectSelect.query(Artist.class)
143+
.prefetch(Artist.PAINTING_ARRAY.disjoint())
144+
.prefetch(Artist.PAINTING_ARRAY.dot(Painting.TO_PAINTING_INFO).disjoint())
145+
.prefetch(Artist.PAINTING_ARRAY.dot(Painting.TO_GALLERY).disjoint())
146+
.orderBy(Artist.ARTIST_ID_PK_PROPERTY.asc())
147+
.select(context);
148+
149+
List<Artist> artistsFiltered = exp.filterObjects(artists);
150+
assertEquals(exp.toString(), artistSelected, artistsFiltered);
151+
}
152+
}

0 commit comments

Comments
 (0)