You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cayenne.apache.org by aa...@apache.org on 2026/09/20 16:45:33 UTC

(cayenne) branch master updated: Proper exists and friends to string conversion

This is an automated email from the ASF dual-hosted git repository.

asf-gitbox-commits pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cayenne.git


The following commit(s) were added to refs/heads/master by this push:
     new 8ece7019f Proper exists and friends to string conversion
8ece7019f is described below

commit 8ece7019fedbf79b371f5bb18a1aec9cc4f49db3
Author: Andrus Adamchik <an...@objectstyle.com>
AuthorDate: Sun Sep 20 12:45:26 2026 -0400

    Proper exists and friends to string conversion
---
 .../java/org/apache/cayenne/exp/parser/ASTAll.java | 12 +++++
 .../java/org/apache/cayenne/exp/parser/ASTAny.java | 12 +++++
 .../org/apache/cayenne/exp/parser/ASTExists.java   | 19 +++++++
 .../apache/cayenne/exp/parser/ASTNotExists.java    | 20 +++++++
 .../org/apache/cayenne/exp/parser/ASTSubquery.java |  3 +-
 .../apache/cayenne/exp/parser/ASTExistsTest.java   | 17 +++++-
 .../cayenne/exp/parser/ASTNotExistsTest.java       | 17 +++++-
 .../apache/cayenne/exp/parser/ASTSubqueryTest.java | 62 ++++++++++++++++++++++
 8 files changed, 159 insertions(+), 3 deletions(-)

diff --git a/cayenne/src/main/java/org/apache/cayenne/exp/parser/ASTAll.java b/cayenne/src/main/java/org/apache/cayenne/exp/parser/ASTAll.java
index 96a58f11c..194c23e65 100644
--- a/cayenne/src/main/java/org/apache/cayenne/exp/parser/ASTAll.java
+++ b/cayenne/src/main/java/org/apache/cayenne/exp/parser/ASTAll.java
@@ -19,6 +19,8 @@
 
 package org.apache.cayenne.exp.parser;
 
+import java.io.IOException;
+
 import org.apache.cayenne.exp.Expression;
 
 public class ASTAll extends ConditionNode {
@@ -46,6 +48,16 @@ public class ASTAll extends ConditionNode {
         return null;
     }
 
+    /**
+     * @since 5.0
+     */
+    @Override
+    public void appendAsString(Appendable out) throws IOException {
+        // the operator goes before the single operand, so it is never printed by the superclass
+        out.append("all ");
+        ((SimpleNode) jjtGetChild(0)).appendAsString(out);
+    }
+
     @Override
     protected String getExpressionOperator(int index) {
         return "ALL";
diff --git a/cayenne/src/main/java/org/apache/cayenne/exp/parser/ASTAny.java b/cayenne/src/main/java/org/apache/cayenne/exp/parser/ASTAny.java
index 4ab2f6b98..28075df74 100644
--- a/cayenne/src/main/java/org/apache/cayenne/exp/parser/ASTAny.java
+++ b/cayenne/src/main/java/org/apache/cayenne/exp/parser/ASTAny.java
@@ -19,6 +19,8 @@
 
 package org.apache.cayenne.exp.parser;
 
+import java.io.IOException;
+
 import org.apache.cayenne.exp.Expression;
 
 public class ASTAny extends ConditionNode {
@@ -51,6 +53,16 @@ public class ASTAny extends ConditionNode {
         return true;
     }
 
+    /**
+     * @since 5.0
+     */
+    @Override
+    public void appendAsString(Appendable out) throws IOException {
+        // the operator goes before the single operand, so it is never printed by the superclass
+        out.append("any ");
+        ((SimpleNode) jjtGetChild(0)).appendAsString(out);
+    }
+
     @Override
     protected String getExpressionOperator(int index) {
         return "ANY";
diff --git a/cayenne/src/main/java/org/apache/cayenne/exp/parser/ASTExists.java b/cayenne/src/main/java/org/apache/cayenne/exp/parser/ASTExists.java
index ffacd9f7a..e3a6cce5f 100644
--- a/cayenne/src/main/java/org/apache/cayenne/exp/parser/ASTExists.java
+++ b/cayenne/src/main/java/org/apache/cayenne/exp/parser/ASTExists.java
@@ -23,6 +23,7 @@ import org.apache.cayenne.Persistent;
 import org.apache.cayenne.exp.Expression;
 import org.apache.cayenne.exp.ExpressionFactory;
 
+import java.io.IOException;
 import java.util.Collection;
 import java.util.Map;
 
@@ -78,6 +79,24 @@ public class ASTExists extends ConditionNode {
         return Boolean.FALSE;
     }
 
+    /**
+     * @since 5.0
+     */
+    @Override
+    public void appendAsString(Appendable out) throws IOException {
+        // the operator goes before the single operand, so it is never printed by the superclass
+        if (parent != null) {
+            out.append('(');
+        }
+
+        out.append("exists ");
+        ((SimpleNode) jjtGetChild(0)).appendAsString(out);
+
+        if (parent != null) {
+            out.append(')');
+        }
+    }
+
     @Override
     protected String getExpressionOperator(int index) {
         return null;
diff --git a/cayenne/src/main/java/org/apache/cayenne/exp/parser/ASTNotExists.java b/cayenne/src/main/java/org/apache/cayenne/exp/parser/ASTNotExists.java
index 7348d0c14..6a1fbe8ae 100644
--- a/cayenne/src/main/java/org/apache/cayenne/exp/parser/ASTNotExists.java
+++ b/cayenne/src/main/java/org/apache/cayenne/exp/parser/ASTNotExists.java
@@ -19,6 +19,8 @@
 
 package org.apache.cayenne.exp.parser;
 
+import java.io.IOException;
+
 import org.apache.cayenne.exp.Expression;
 import org.apache.cayenne.exp.ExpressionFactory;
 
@@ -55,6 +57,24 @@ public class ASTNotExists extends ConditionNode {
         return !ASTExists.notEmpty(o);
     }
 
+    /**
+     * @since 5.0
+     */
+    @Override
+    public void appendAsString(Appendable out) throws IOException {
+        // the operator goes before the single operand, so it is never printed by the superclass
+        if (parent != null) {
+            out.append('(');
+        }
+
+        out.append("not exists ");
+        ((SimpleNode) jjtGetChild(0)).appendAsString(out);
+
+        if (parent != null) {
+            out.append(')');
+        }
+    }
+
     @Override
     protected String getExpressionOperator(int index) {
         return null;
diff --git a/cayenne/src/main/java/org/apache/cayenne/exp/parser/ASTSubquery.java b/cayenne/src/main/java/org/apache/cayenne/exp/parser/ASTSubquery.java
index 94f28f526..bf2aacd7a 100644
--- a/cayenne/src/main/java/org/apache/cayenne/exp/parser/ASTSubquery.java
+++ b/cayenne/src/main/java/org/apache/cayenne/exp/parser/ASTSubquery.java
@@ -91,7 +91,8 @@ public class ASTSubquery extends SimpleNode {
 
     @Override
     public void appendAsString(Appendable out) throws IOException {
-        out.append("EXISTS");
+        // there is no String form for a query, so this is just a placeholder
+        out.append("(select ...)");
     }
 
     public FluentSelect<?, ?> getQuery() {
diff --git a/cayenne/src/test/java/org/apache/cayenne/exp/parser/ASTExistsTest.java b/cayenne/src/test/java/org/apache/cayenne/exp/parser/ASTExistsTest.java
index e9f4a4a73..e9d7a8b5b 100644
--- a/cayenne/src/test/java/org/apache/cayenne/exp/parser/ASTExistsTest.java
+++ b/cayenne/src/test/java/org/apache/cayenne/exp/parser/ASTExistsTest.java
@@ -68,4 +68,19 @@ public class ASTExistsTest {
         assertEquals("(length(a) <= 5)", node.jjtGetChild(0).toString());
     }
 
-}
\ No newline at end of file
+    @Test
+    public void toStringRoundTrip() {
+        for (String expString : new String[]{
+                "exists a",
+                "exists a.b.c",
+                "exists db:a.b.c",
+                "exists (a > 5)",
+                "(x = 1) and (exists a)",
+                "(x = 1) or (exists (a > 5))"}) {
+
+            Expression exp = ExpressionFactory.exp(expString);
+            assertEquals(expString, exp.toString());
+            assertEquals(exp, ExpressionFactory.exp(exp.toString()));
+        }
+    }
+}
diff --git a/cayenne/src/test/java/org/apache/cayenne/exp/parser/ASTNotExistsTest.java b/cayenne/src/test/java/org/apache/cayenne/exp/parser/ASTNotExistsTest.java
index 5015c29ce..213a9f226 100644
--- a/cayenne/src/test/java/org/apache/cayenne/exp/parser/ASTNotExistsTest.java
+++ b/cayenne/src/test/java/org/apache/cayenne/exp/parser/ASTNotExistsTest.java
@@ -68,4 +68,19 @@ public class ASTNotExistsTest {
         assertEquals("(length(a) <= 5)", node.jjtGetChild(0).toString());
     }
 
-}
\ No newline at end of file
+    @Test
+    public void toStringRoundTrip() {
+        for (String expString : new String[]{
+                "not exists a",
+                "not exists a.b.c",
+                "not exists db:a.b.c",
+                "not exists (a > 5)",
+                "(x = 1) and (not exists a)",
+                "(x = 1) or (not exists (a > 5))"}) {
+
+            Expression exp = ExpressionFactory.exp(expString);
+            assertEquals(expString, exp.toString());
+            assertEquals(exp, ExpressionFactory.exp(exp.toString()));
+        }
+    }
+}
diff --git a/cayenne/src/test/java/org/apache/cayenne/exp/parser/ASTSubqueryTest.java b/cayenne/src/test/java/org/apache/cayenne/exp/parser/ASTSubqueryTest.java
new file mode 100644
index 000000000..bcda6bd23
--- /dev/null
+++ b/cayenne/src/test/java/org/apache/cayenne/exp/parser/ASTSubqueryTest.java
@@ -0,0 +1,62 @@
+/*****************************************************************
+ *   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.exp.parser;
+
+import org.apache.cayenne.exp.Expression;
+import org.apache.cayenne.exp.ExpressionFactory;
+import org.apache.cayenne.query.ColumnSelect;
+import org.apache.cayenne.query.ObjectSelect;
+import org.apache.cayenne.testdo.testmap.Artist;
+import org.apache.cayenne.testdo.testmap.Painting;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class ASTSubqueryTest {
+
+    private static final ColumnSelect<String> NAMES = ObjectSelect.columnQuery(Artist.class, Artist.ARTIST_NAME);
+
+    @Test
+    public void existsToString() {
+        Expression exists = ExpressionFactory.exists(ObjectSelect.query(Painting.class));
+        assertEquals("exists (select ...)", exists.toString());
+
+        Expression notExists = ExpressionFactory.notExists(ObjectSelect.query(Painting.class));
+        assertEquals("not exists (select ...)", notExists.toString());
+
+        assertEquals("(a = 1) and (not exists (select ...))",
+                ExpressionFactory.exp("a = 1").andExp(notExists).toString());
+    }
+
+    @Test
+    public void inToString() {
+        assertEquals("a in (select ...)", ExpressionFactory.inExp(ExpressionFactory.pathExp("a"), NAMES).toString());
+        assertEquals("a not in (select ...)",
+                ExpressionFactory.notInExp(ExpressionFactory.pathExp("a"), NAMES).toString());
+    }
+
+    @Test
+    public void allAndAnyToString() {
+        assertEquals("a > all (select ...)",
+                ExpressionFactory.greaterExp("a", ExpressionFactory.all(NAMES)).toString());
+        assertEquals("a = any (select ...)",
+                ExpressionFactory.matchExp("a", ExpressionFactory.any(NAMES)).toString());
+    }
+}