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 ef17c9402 CAY-2988 Nested iterated queries transaction handling
ef17c9402 is described below

commit ef17c9402a3e57d2fd837f752cf1c473dd11e240
Author: Andrus Adamchik <[email protected]>
AuthorDate: Mon Aug 17 12:44:55 2026 -0400

    CAY-2988 Nested iterated queries transaction handling
---
 RELEASE-NOTES.txt                                  |  1 +
 .../cayenne/access/DataDomainQueryAction.java      | 40 ++++++++++++----------
 2 files changed, 23 insertions(+), 18 deletions(-)

diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt
index f65d75126..e3e641c2b 100644
--- a/RELEASE-NOTES.txt
+++ b/RELEASE-NOTES.txt
@@ -51,6 +51,7 @@ CAY-2968 Vertical Inheritance: INSERT instead of UPDATE after 
updating flattened
 CAY-2973 Exception trying to copy/paste a callback
 CAY-2976 Exception creating a relationship for an Incomplete ObjEntity
 CAY-2977 DbImport confused by multi-key relationships
+CAY-2988 Nested iterated queries transaction handling
 
 ----------------------------------
 Release: 5.0-M2
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/DataDomainQueryAction.java 
b/cayenne/src/main/java/org/apache/cayenne/access/DataDomainQueryAction.java
index e6e80d8f2..bfe9fb3a6 100644
--- a/cayenne/src/main/java/org/apache/cayenne/access/DataDomainQueryAction.java
+++ b/cayenne/src/main/java/org/apache/cayenne/access/DataDomainQueryAction.java
@@ -73,6 +73,7 @@ import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import java.util.function.Function;
 
@@ -179,16 +180,27 @@ class DataDomainQueryAction implements QueryRouter, 
OperationObserver {
     }
 
     private void performIteratedQuery() {
-        Transaction tx = BaseTransaction.getThreadTransaction();
-        if (tx != null) {
-            runIteratedQuery(tx);
-        } else {
-            tx = domain.getTransactionFactory().createTransaction();
+
+        // The transaction is owned by the caller, and is not aligned with the 
iterator scope
+        if (BaseTransaction.getThreadTransaction() != null) {
+            runQuery();
+            Objects.requireNonNull(fullResponse.firstIterator(), "Iterator 
response expected");
+        }
+
+        // The transaction will be owned by the iterator, and will outlive the 
query action
+        else {
+            Transaction tx = 
domain.getTransactionFactory().createTransaction();
             BaseTransaction.bindThreadTransaction(tx);
             try {
-                runIteratedQuery(tx);
-            } catch (Exception e) {
-                throw new CayenneRuntimeException(e);
+                runQuery();
+                ResultIterator<?> it = 
Objects.requireNonNull(fullResponse.firstIterator(), "Iterator response 
expected");
+                fullResponse.replaceResult(it, new 
TransactionResultIteratorDecorator<>(it, tx));
+            } catch (Throwable th) {
+                // No iterator will be returned to the caller, so nothing will 
ever commit this transaction. Mark it
+                // for rollback below, so that its connections are not leaked. 
Note that a DataNode marks the
+                // transaction on its own, but a failure may as well originate 
outside of the node.
+                tx.setRollbackOnly();
+                throw th;
             } finally {
                 BaseTransaction.bindThreadTransaction(null);
                 if (tx.isRollbackOnly()) {
@@ -199,6 +211,8 @@ class DataDomainQueryAction implements QueryRouter, 
OperationObserver {
                 }
             }
         }
+
+        fullResponse.reset();
     }
 
     private boolean interceptOIDQuery() {
@@ -525,16 +539,6 @@ class DataDomainQueryAction implements QueryRouter, 
OperationObserver {
         }
     }
 
-    private void runIteratedQuery(Transaction tx) {
-        runQuery();
-        ResultIterator<?> iterator = fullResponse.firstIterator();
-        if (iterator == null) {
-            throw new IllegalStateException("Iterator response expected");
-        }
-        fullResponse.replaceResult(iterator, new 
TransactionResultIteratorDecorator<>(iterator, tx));
-        fullResponse.reset();
-    }
-
     @SuppressWarnings({"unchecked", "rawtypes"})
     private void interceptObjectConversion() {
         if (noObjectConversion()) {

Reply via email to