findSingleAttributeOrEmpty() {
+ return query.findSingleAttributeOrEmpty();
+ }
+
/**
* Execute the query processing the beans one at a time.
*
diff --git a/ebean-querybean/src/test/java/org/example/domain/Contact.java b/ebean-querybean/src/test/java/org/example/domain/Contact.java
index 7d2554f57..505cb556a 100644
--- a/ebean-querybean/src/test/java/org/example/domain/Contact.java
+++ b/ebean-querybean/src/test/java/org/example/domain/Contact.java
@@ -1,8 +1,9 @@
package org.example.domain;
import io.ebean.annotation.DbArray;
-
import jakarta.persistence.*;
+import org.example.domain.finder.ContactFinder;
+
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.time.ZonedDateTime;
@@ -17,6 +18,11 @@ import java.util.Map;
@Table(name = "be_contact")
public class Contact extends BaseModel {
+ /**
+ * Convenience Finder for 'active record' style.
+ */
+ public static final ContactFinder find = new ContactFinder();
+
@DbArray
List<@Size(max=20) String> phoneNumbers = new ArrayList<>();
diff --git a/ebean-querybean/src/test/java/org/example/domain/finder/ContactFinder.java b/ebean-querybean/src/test/java/org/example/domain/finder/ContactFinder.java
new file mode 100644
index 000000000..42f0daccb
--- /dev/null
+++ b/ebean-querybean/src/test/java/org/example/domain/finder/ContactFinder.java
@@ -0,0 +1,18 @@
+package org.example.domain.finder;
+
+import io.ebean.Finder;
+import org.example.domain.Contact;
+import org.example.domain.query.QContact;
+
+/**
+ */
+public class ContactFinder extends Finder {
+
+ public ContactFinder() {
+ super(Contact.class);
+ }
+
+ public QContact typed() {
+ return new QContact();
+ }
+}
diff --git a/ebean-querybean/src/test/java/org/querytest/QCustomerAndOrTest.java b/ebean-querybean/src/test/java/org/querytest/QCustomerAndOrTest.java
index b930c1509..7862e0dcd 100644
--- a/ebean-querybean/src/test/java/org/querytest/QCustomerAndOrTest.java
+++ b/ebean-querybean/src/test/java/org/querytest/QCustomerAndOrTest.java
@@ -1,6 +1,8 @@
package org.querytest;
+import org.example.domain.Contact;
import org.example.domain.Customer;
+import org.example.domain.query.QCustomer;
import org.junit.jupiter.api.Test;
import java.time.LocalDate;
@@ -9,6 +11,8 @@ import java.time.ZoneId;
import java.util.Date;
import java.util.List;
+import static org.assertj.core.api.Assertions.assertThat;
+
public class QCustomerAndOrTest {
@Test
@@ -55,6 +59,30 @@ public class QCustomerAndOrTest {
}
+ @Test
+ public void testOrWithExists() {
+ QCustomer query = Customer.find.typed()
+ .alias("_cust")
+ .or()
+ .name.eq("Superman")
+ .exists(Contact.find.typed()
+ .alias("contact")
+ .firstName.eq("Superman")
+ .raw("contact.customer_id = _cust.id")
+ .query()
+ )
+ .endOr()
+ .select(QCustomer.alias().id);
+
+ query.findList();
+
+ assertThat(query.getGeneratedSql()).isEqualTo(
+ "select _cust.id from be_customer _cust where (" +
+ "_cust.name = ? or exists (select 1 from be_contact contact where " +
+ "contact.first_name = ? and contact.customer_id = _cust.id))"
+ );
+ }
+
private Date fiveDaysAgo() {
LocalDateTime fiveDaysAgo = LocalDate.now().atStartOfDay().minusDays(5);
return new Date(fiveDaysAgo.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
diff --git a/ebean-querybean/src/test/java/org/querytest/QCustomerTest.java b/ebean-querybean/src/test/java/org/querytest/QCustomerTest.java
index d2a8bedff..5c25c91e9 100644
--- a/ebean-querybean/src/test/java/org/querytest/QCustomerTest.java
+++ b/ebean-querybean/src/test/java/org/querytest/QCustomerTest.java
@@ -63,6 +63,25 @@ public class QCustomerTest {
}
+ @Test
+ public void copy() {
+ var origin = new QCustomer()
+ .setDistinct(true)
+ .status.equalTo(Customer.Status.BAD);
+
+ var copy1 = origin.copy().name.isNotNull();
+ var q1 = copy1.query();
+ copy1.findList();
+
+ assertThat(q1.getGeneratedSql()).contains("from be_customer t0 where t0.status = ? and t0.name is not null");
+
+ var copy2 = origin.copy().version.ge(1L);
+ var q2 = copy2.query();
+ copy2.findList();
+
+ assertThat(q2.getGeneratedSql()).contains("from be_customer t0 where t0.status = ? and t0.version >= ?");
+ }
+
@Test
public void findSingleAttribute() {
@@ -1050,6 +1069,32 @@ public class QCustomerTest {
assertThat(maxDate).isNotNull();
}
+ @Test
+ public void findSingleAttributeOrEmpty() {
+
+ Customer cust = new Customer();
+ cust.setName("MaybeIExist yeah");
+ cust.setStatus(Customer.Status.GOOD);
+ cust.setRegistered(new Date());
+ cust.save();
+
+ Optional customerName = new QCustomer()
+ .select(name)
+ .status.eq(Customer.Status.GOOD)
+ .name.startsWith("MaybeIExist")
+ .findSingleAttributeOrEmpty();
+
+ assertThat(customerName).isPresent();
+
+ Optional customerName2 = new QCustomer()
+ .select(name)
+ .status.eq(Customer.Status.GOOD)
+ .name.eq("NahIDoNotExist")
+ .findSingleAttributeOrEmpty();
+
+ assertThat(customerName2).isEmpty();
+ }
+
@Test
public void testFetchByScalarValue() {
diff --git a/ebean-redis/pom.xml b/ebean-redis/pom.xml
index cef3e1f38..0c5f5def6 100644
--- a/ebean-redis/pom.xml
+++ b/ebean-redis/pom.xml
@@ -4,7 +4,7 @@
ebean-parent
io.ebean
- 13.25.2-jakarta
+ 13.26.1-jakarta
ebean-redis
@@ -22,28 +22,28 @@
io.ebean
ebean-api
- 13.25.2-jakarta
+ 13.26.1-jakarta
provided
io.ebean
ebean-core
- 13.25.2-jakarta
+ 13.26.1-jakarta
provided
io.ebean
ebean-querybean
- 13.25.2-jakarta
+ 13.26.1-jakarta
test
io.ebean
ebean-test
- 13.25.2-jakarta
+ 13.26.1-jakarta
test
@@ -57,7 +57,7 @@
io.ebean
querybean-generator
- 13.25.2-jakarta
+ 13.26.1-jakarta
provided
diff --git a/ebean-spring-txn/pom.xml b/ebean-spring-txn/pom.xml
index 55055649d..f9e9e5eec 100644
--- a/ebean-spring-txn/pom.xml
+++ b/ebean-spring-txn/pom.xml
@@ -4,7 +4,7 @@
ebean-parent
io.ebean
- 13.25.2-jakarta
+ 13.26.1-jakarta
ebean-spring-txn
@@ -28,7 +28,7 @@
io.ebean
ebean-core
- 13.25.2-jakarta
+ 13.26.1-jakarta
provided
@@ -77,7 +77,7 @@
io.ebean
ebean-test
- 13.25.2-jakarta
+ 13.26.1-jakarta
test
diff --git a/ebean-test/pom.xml b/ebean-test/pom.xml
index 37e0ac0a8..cc8f2e3ab 100644
--- a/ebean-test/pom.xml
+++ b/ebean-test/pom.xml
@@ -4,7 +4,7 @@
ebean-parent
io.ebean
- 13.25.2-jakarta
+ 13.26.1-jakarta
ebean test
@@ -33,20 +33,20 @@
io.ebean
ebean-platform-h2
- 13.25.2-jakarta
+ 13.26.1-jakarta
io.ebean
ebean-core
- 13.25.2-jakarta
+ 13.26.1-jakarta
provided
io.ebean
ebean-ddl-generator
- 13.25.2-jakarta
+ 13.26.1-jakarta
@@ -149,14 +149,14 @@
io.ebean
ebean-jackson-mapper
- 13.25.2-jakarta
+ 13.26.1-jakarta
test
io.ebean
ebean-platform-all
- 13.25.2-jakarta
+ 13.26.1-jakarta
test
diff --git a/ebean-test/src/test/java/io/ebean/xtest/common/BeanListTest.java b/ebean-test/src/test/java/io/ebean/xtest/common/BeanListTest.java
index 69aea90ff..3285b60a8 100644
--- a/ebean-test/src/test/java/io/ebean/xtest/common/BeanListTest.java
+++ b/ebean-test/src/test/java/io/ebean/xtest/common/BeanListTest.java
@@ -3,6 +3,7 @@ package io.ebean.xtest.common;
import io.ebean.bean.BeanCollection;
import io.ebean.common.BeanList;
import org.junit.jupiter.api.Test;
+import org.tests.model.basic.Product;
import java.util.ArrayList;
import java.util.Comparator;
@@ -13,22 +14,22 @@ import static org.assertj.core.api.Assertions.assertThat;
public class BeanListTest {
- private final Object object1 = new Object();
- private final Object object2 = new Object();
- private final Object object3 = new Object();
+ private final Product object1 = new Product(1);
+ private final Product object2 = new Product(2);
+ private final Product object3 = new Product(3);
private List all() {
List all = new ArrayList<>();
- all.add(object1);
- all.add(object2);
- all.add(object3);
+ all.add(new Product(1));
+ all.add(new Product(2));
+ all.add(new Product(3));
return all;
}
private List some() {
List some = new ArrayList<>();
- some.add(object2);
- some.add(object3);
+ some.add(new Product(2));
+ some.add(new Product(3));
return some;
}
@@ -163,14 +164,15 @@ public class BeanListTest {
@Test
public void testRemove_given_beansInAdditions() {
+ List all = all();
BeanList list = new BeanList<>();
list.setModifyListening(BeanCollection.ModifyListenMode.ALL);
- list.addAll(all());
+ list.addAll(all);
assertThat(list.modifyAdditions()).containsOnly(object1, object2, object3);
// act
- list.remove(object2);
- list.remove(object3);
+ list.remove(all.get(1));
+ list.remove(all.get(2));
assertThat(list.modifyAdditions()).containsOnly(object1);
assertThat(list.modifyRemovals()).isEmpty();
@@ -179,13 +181,14 @@ public class BeanListTest {
@Test
public void testRemoveAll_given_beansInAdditions() {
+ List all = all();
BeanList list = new BeanList<>();
list.setModifyListening(BeanCollection.ModifyListenMode.ALL);
- list.addAll(all());
+ list.addAll(all);
assertThat(list.modifyAdditions()).containsOnly(object1, object2, object3);
// act
- list.removeAll(some());
+ list.removeAll(List.of(all.get(1), all.get(2)));
assertThat(list.modifyAdditions()).containsOnly(object1);
assertThat(list.modifyRemovals()).isEmpty();
diff --git a/ebean-test/src/test/java/io/ebean/xtest/common/BeanMapTest.java b/ebean-test/src/test/java/io/ebean/xtest/common/BeanMapTest.java
index 910d96661..a72a17b2a 100644
--- a/ebean-test/src/test/java/io/ebean/xtest/common/BeanMapTest.java
+++ b/ebean-test/src/test/java/io/ebean/xtest/common/BeanMapTest.java
@@ -3,7 +3,7 @@ package io.ebean.xtest.common;
import io.ebean.bean.BeanCollection;
import io.ebean.common.BeanMap;
import org.junit.jupiter.api.Test;
-import org.tests.model.basic.EBasic;
+import org.tests.model.basic.Product;
import java.util.*;
@@ -14,24 +14,24 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
public class BeanMapTest {
- private final EBasic object1 = new EBasic("o1");
- private final EBasic object2 = new EBasic("o2");
- private final EBasic object3 = new EBasic("o3");
- private final EBasic object4 = new EBasic("o4");
- private final EBasic object5 = new EBasic("o5");
+ private final Product object1 = new Product(1);
+ private final Product object2 = new Product(2);
+ private final Product object3 = new Product(3);
+ private final Product object4 = new Product(4);
+ private final Product object5 = new Product(5);
- private LinkedHashMap all() {
- LinkedHashMap all = new LinkedHashMap<>();
- all.put("1", object1);
- all.put("2", object2);
- all.put("3", object3);
+ private Map all() {
+ Map all = new LinkedHashMap<>();
+ all.put("1", new Product(1));
+ all.put("2", new Product(2));
+ all.put("3", new Product(3));
return all;
}
- private LinkedHashMap some() {
- LinkedHashMap all = new LinkedHashMap<>();
- all.put("2", object2);
- all.put("3", object3);
+ private Map some() {
+ Map all = new LinkedHashMap<>();
+ all.put("2", new Product(2));
+ all.put("3", new Product(3));
return all;
}
@@ -73,8 +73,8 @@ public class BeanMapTest {
@Test
public void testAdd_given_someAlreadyIn() {
-
- BeanMap map = new BeanMap<>(some());
+ Map some = new LinkedHashMap<>(Map.of("2", object2, "3", object3));
+ BeanMap map = new BeanMap<>(some);
map.setModifyListening(BeanCollection.ModifyListenMode.ALL);
// act
@@ -90,11 +90,13 @@ public class BeanMapTest {
@Test
public void testAddSome_given_someAlreadyIn() {
- BeanMap map = new BeanMap<>(some());
+ Map some = new LinkedHashMap<>(Map.of("2", object2, "3", object3));
+ BeanMap map = new BeanMap<>(some);
map.setModifyListening(BeanCollection.ModifyListenMode.ALL);
// act
- map.putAll(all());
+ Map all = Map.of("1", object1, "2", object2, "3", object3);
+ map.putAll(all);
assertThat(map.modifyAdditions()).containsOnly(object1);
assertThat(map.modifyRemovals()).isEmpty();
@@ -179,7 +181,7 @@ public class BeanMapTest {
@Test
public void testClear_given_someBeansInAdditions() {
- BeanMap map = newModifyListeningMap();
+ BeanMap map = newModifyListeningMap();
map.put("2", object2);
map.put("3", object3);
@@ -193,7 +195,7 @@ public class BeanMapTest {
@Test
public void keySet_add_whenModifyListening() {
- BeanMap map = newModifyListeningMap();
+ BeanMap map = newModifyListeningMap();
assertThrows(UnsupportedOperationException.class, () -> map.keySet().add("3"));
}
@@ -205,7 +207,7 @@ public class BeanMapTest {
@Test
public void keySet_addAll_whenModifyListening() {
- BeanMap map = newModifyListeningMap();
+ BeanMap map = newModifyListeningMap();
assertThrows(UnsupportedOperationException.class, () -> map.keySet().addAll(asList("3", "4")));
}
@@ -317,13 +319,13 @@ public class BeanMapTest {
@Test
public void values_add() {
- BeanMap map = new BeanMap<>();
+ BeanMap map = new BeanMap<>();
assertThrows(UnsupportedOperationException.class, () -> map.values().add(object3));
}
@Test
public void values_addAll() {
- BeanMap map = new BeanMap<>();
+ BeanMap map = new BeanMap<>();
assertThrows(UnsupportedOperationException.class, () -> map.values().addAll(asList(object3, object5)));
}
@@ -337,8 +339,8 @@ public class BeanMapTest {
@Test
public void entrySet_clear() {
- final BeanMap map = newModifyListeningMap();
- final Set> entries = map.entrySet();
+ final BeanMap map = newModifyListeningMap();
+ final Set> entries = map.entrySet();
entries.clear();
assertThat(entries).isEmpty();
@@ -348,8 +350,8 @@ public class BeanMapTest {
@Test
public void entrySet_remove() {
- final BeanMap map = newModifyListeningMap5();
- final Set> entries = map.entrySet();
+ final BeanMap map = newModifyListeningMap5();
+ final Set> entries = map.entrySet();
assertThat(map).hasSize(5);
@@ -365,8 +367,8 @@ public class BeanMapTest {
@Test
public void entrySet_remove_whenNotEqualValue() {
- final BeanMap map = newModifyListeningMap5();
- final Set> entries = map.entrySet();
+ final BeanMap map = newModifyListeningMap5();
+ final Set> entries = map.entrySet();
assertThat(map).hasSize(5);
@@ -379,11 +381,11 @@ public class BeanMapTest {
@Test
public void entrySet_iterator_remove() {
- final BeanMap map = newModifyListeningMap5();
- final Set> entries = map.entrySet();
- final Iterator> iterator = entries.iterator();
+ final BeanMap map = newModifyListeningMap5();
+ final Set> entries = map.entrySet();
+ final Iterator> iterator = entries.iterator();
while (iterator.hasNext()) {
- final Map.Entry entry = iterator.next();
+ final Map.Entry entry = iterator.next();
if (entry.getKey().equals("2") || entry.getKey().equals("5")) {
iterator.remove();
}
@@ -395,8 +397,8 @@ public class BeanMapTest {
@Test
public void entrySet_removeAll() {
- final BeanMap map = newModifyListeningMap5();
- final Set> entries = map.entrySet();
+ final BeanMap map = newModifyListeningMap5();
+ final Set> entries = map.entrySet();
entries.removeAll(asList(new AbstractMap.SimpleEntry<>("1", object1), new AbstractMap.SimpleEntry<>("3", object4), new AbstractMap.SimpleEntry<>("4", object4)));
assertThat(map).hasSize(3);
@@ -406,8 +408,8 @@ public class BeanMapTest {
@Test
public void entrySet_retainAll() {
- final BeanMap map = newModifyListeningMap5();
- final Set> entries = map.entrySet();
+ final BeanMap map = newModifyListeningMap5();
+ final Set> entries = map.entrySet();
entries.retainAll(asList(new AbstractMap.SimpleEntry<>("1", object1), new AbstractMap.SimpleEntry<>("3", object4), new AbstractMap.SimpleEntry<>("4", object4)));
assertThat(map).hasSize(2);
@@ -415,15 +417,15 @@ public class BeanMapTest {
assertThat(map.modifyRemovals()).containsOnly(object2, object3, object5);
}
- private BeanMap newModifyListeningMap() {
- BeanMap map = new BeanMap<>();
+ private BeanMap newModifyListeningMap() {
+ BeanMap map = new BeanMap<>();
map.put("1", object1);
map.setModifyListening(BeanCollection.ModifyListenMode.ALL);
return map;
}
- private BeanMap newModifyListeningMap5() {
- BeanMap map = new BeanMap<>();
+ private BeanMap newModifyListeningMap5() {
+ BeanMap map = new BeanMap<>();
map.put("1", object1);
map.put("2", object2);
map.put("3", object3);
diff --git a/ebean-test/src/test/java/io/ebean/xtest/common/BeanSetTest.java b/ebean-test/src/test/java/io/ebean/xtest/common/BeanSetTest.java
index ca265affd..2152af71b 100644
--- a/ebean-test/src/test/java/io/ebean/xtest/common/BeanSetTest.java
+++ b/ebean-test/src/test/java/io/ebean/xtest/common/BeanSetTest.java
@@ -3,6 +3,7 @@ package io.ebean.xtest.common;
import io.ebean.bean.BeanCollection;
import io.ebean.common.BeanSet;
import org.junit.jupiter.api.Test;
+import org.tests.model.basic.Product;
import java.util.LinkedHashSet;
import java.util.Set;
@@ -12,22 +13,22 @@ import static org.assertj.core.api.Assertions.assertThat;
public class BeanSetTest {
- Object object1 = new Object();
- Object object2 = new Object();
- Object object3 = new Object();
+ Product object1 = new Product(1);
+ Product object2 = new Product(2);
+ Product object3 = new Product(3);
- private LinkedHashSet all() {
- var all = new LinkedHashSet<>();
- all.add(object1);
- all.add(object2);
- all.add(object3);
+ private Set all() {
+ Set all = new LinkedHashSet<>();
+ all.add(new Product(1));
+ all.add(new Product(2));
+ all.add(new Product(3));
return all;
}
- private LinkedHashSet some() {
- var some = new LinkedHashSet<>();
- some.add(object2);
- some.add(object3);
+ private Set some() {
+ Set some = new LinkedHashSet<>();
+ some.add(new Product(2));
+ some.add(new Product(3));
return some;
}
@@ -99,7 +100,9 @@ public class BeanSetTest {
BeanSet set = new BeanSet<>();
set.setModifyListening(BeanCollection.ModifyListenMode.ALL);
- set.addAll(all());
+
+ Set all = Set.of(object1, object2, object3);
+ set.addAll(all);
assertThat(set.modifyAdditions()).containsOnly(object1, object2, object3);
// act
@@ -115,11 +118,13 @@ public class BeanSetTest {
BeanSet set = new BeanSet<>();
set.setModifyListening(BeanCollection.ModifyListenMode.ALL);
- set.addAll(all());
+
+ Set all = Set.of(object1, object2, object3);
+ set.addAll(all);
assertThat(set.modifyAdditions()).containsOnly(object1, object2, object3);
// act
- set.removeAll(some());
+ set.removeAll(Set.of(object2, object3));
assertThat(set.modifyAdditions()).containsOnly(object1);
assertThat(set.modifyRemovals()).isEmpty();
diff --git a/ebean-test/src/test/java/io/ebean/xtest/internal/api/TDSpiServer.java b/ebean-test/src/test/java/io/ebean/xtest/internal/api/TDSpiServer.java
index 6efedcacb..fefece847 100644
--- a/ebean-test/src/test/java/io/ebean/xtest/internal/api/TDSpiServer.java
+++ b/ebean-test/src/test/java/io/ebean/xtest/internal/api/TDSpiServer.java
@@ -453,21 +453,41 @@ public class TDSpiServer implements SpiServer {
}
+ @Override
+ public void insert(Object bean, InsertOptions insertOptions) {
+
+ }
+
@Override
public void insert(Object bean, Transaction transaction) {
}
+ @Override
+ public void insert(Object bean, InsertOptions insertOptions, Transaction transaction) {
+
+ }
+
@Override
public void insertAll(Collection> beans) {
}
+ @Override
+ public void insertAll(Collection> beans, InsertOptions options) {
+
+ }
+
@Override
public void insertAll(Collection> beans, Transaction transaction) {
}
+ @Override
+ public void insertAll(Collection> beans, InsertOptions options, Transaction transaction) {
+
+ }
+
@Override
public int execute(SqlUpdate updSql, Transaction transaction) {
return 0;
diff --git a/ebean-test/src/test/java/org/tests/basic/TestQueryForUpdate.java b/ebean-test/src/test/java/org/tests/basic/TestQueryForUpdate.java
index 5b331a59b..75b0c3e6c 100644
--- a/ebean-test/src/test/java/org/tests/basic/TestQueryForUpdate.java
+++ b/ebean-test/src/test/java/org/tests/basic/TestQueryForUpdate.java
@@ -27,14 +27,14 @@ public class TestQueryForUpdate extends BaseTestCase {
try (final Transaction transaction = DB.beginTransaction()) {
query = DB.find(Customer.class)
.forUpdate()
- .order().desc("id");
+ .orderBy().desc("id");
query.findList();
}
if (isSqlServer()) {
assertThat(sqlOf(query)).contains("with (updlock)");
- } else if (!isDb2()){
+ } else {
assertThat(sqlOf(query)).contains("for update");
}
}
@@ -48,21 +48,21 @@ public class TestQueryForUpdate extends BaseTestCase {
query = DB.find(Customer.class)
.forUpdate()
.setMaxRows(3)
- .order().desc("id");
+ .orderBy().desc("id");
query.findList();
}
if (isSqlServer()) {
assertThat(sqlOf(query)).contains("with (updlock)");
- } else if (!isOracle() && !isDb2()) {
+ } else if (!isOracle()) {
// Oracle does not support FOR UPDATE with FETCH
assertThat(sqlOf(query)).contains("for update");
}
}
@Test
- @ForPlatform({Platform.H2, Platform.ORACLE, Platform.POSTGRES, Platform.SQLSERVER, Platform.MYSQL, Platform.MARIADB})
+ @ForPlatform({Platform.H2, Platform.ORACLE, Platform.POSTGRES, Platform.SQLSERVER, Platform.MYSQL, Platform.MARIADB, Platform.DB2})
public void testForUpdate_when_alreadyInPCAsReference() {
ResetBasicData.reset();
Order o0 = DB.find(Order.class).orderBy("id").setMaxRows(1).findOne();
@@ -96,7 +96,7 @@ public class TestQueryForUpdate extends BaseTestCase {
}
@Test
- @ForPlatform({Platform.H2, Platform.ORACLE, Platform.POSTGRES, Platform.SQLSERVER, Platform.MYSQL, Platform.MARIADB})
+ @ForPlatform({Platform.H2, Platform.ORACLE, Platform.POSTGRES, Platform.SQLSERVER, Platform.MYSQL, Platform.MARIADB, Platform.DB2})
public void testForUpdate_when_alreadyInPCAsReference_usingLock() {
ResetBasicData.reset();
Order o0 = DB.find(Order.class).orderBy("id").setMaxRows(1).findOne();
@@ -131,7 +131,7 @@ public class TestQueryForUpdate extends BaseTestCase {
}
@Test
- @ForPlatform({Platform.H2, Platform.ORACLE, Platform.POSTGRES, Platform.SQLSERVER, Platform.MYSQL, Platform.MARIADB})
+ @ForPlatform({Platform.H2, Platform.ORACLE, Platform.POSTGRES, Platform.SQLSERVER, Platform.MYSQL, Platform.MARIADB, Platform.DB2})
public void testForUpdate_when_alreadyInPC() {
EBasic basic = new EBasic("initialValue");
@@ -193,7 +193,7 @@ public class TestQueryForUpdate extends BaseTestCase {
Query query = DB.find(Customer.class)
.forUpdateNoWait()
- .order().desc("id");
+ .orderBy().desc("id");
query.findList();
if (isOracle()) {
@@ -219,7 +219,7 @@ public class TestQueryForUpdate extends BaseTestCase {
Query query = DB.find(Customer.class)
.forUpdateNoWait()
.setMaxRows(1)
- .order().desc("id");
+ .orderBy().desc("id");
List list = query.findList();
Customer first = list.get(0);
diff --git a/ebean-test/src/test/java/org/tests/insert/TestInsertOnConflict.java b/ebean-test/src/test/java/org/tests/insert/TestInsertOnConflict.java
new file mode 100644
index 000000000..4490a87ef
--- /dev/null
+++ b/ebean-test/src/test/java/org/tests/insert/TestInsertOnConflict.java
@@ -0,0 +1,297 @@
+package org.tests.insert;
+
+import io.ebean.DB;
+import io.ebean.Database;
+import io.ebean.InsertOptions;
+import io.ebean.Transaction;
+import io.ebean.annotation.Platform;
+import io.ebean.test.LoggedSql;
+import io.ebean.xtest.BaseTestCase;
+import io.ebean.xtest.ForPlatform;
+import org.junit.jupiter.api.Test;
+import org.tests.update.EPersonOnline;
+
+import java.util.List;
+
+import static io.ebean.InsertOptions.ON_CONFLICT_NOTHING;
+import static io.ebean.InsertOptions.ON_CONFLICT_UPDATE;
+import static org.assertj.core.api.Assertions.assertThat;
+
+class TestInsertOnConflict extends BaseTestCase {
+
+ InsertOptions onConflictDoUpdateAndGetGeneratedKeys = InsertOptions.builder()
+ .onConflictUpdate()
+ .getGeneratedKeys(true)
+ .build();
+
+ @ForPlatform({Platform.POSTGRES, Platform.YUGABYTE})
+ @Test
+ void insertOnConflictUpdateExplicitTransaction() {
+ Database db = DB.getDefault();
+ db.truncate(EPersonOnline.class);
+ LoggedSql.start();
+
+ var bean = newBean("a@b.com");
+
+ try (Transaction txn = DB.createTransaction()) {
+ db.insert(bean, ON_CONFLICT_UPDATE, txn);
+ txn.commit();
+ }
+ assertThat(bean.getId()).isNotNull();
+
+ var bean2 = newBean("a@b.com");
+ bean2.setOnlineStatus(false);
+ try (Transaction txn = DB.createTransaction()) {
+ db.insert(bean2, ON_CONFLICT_UPDATE, txn);
+ txn.commit();
+ }
+ assertThat(bean2.getId()).isEqualTo(bean.getId());
+
+ var sql = LoggedSql.stop();
+ assertThat(sql).hasSize(2);
+ assertThat(sql.get(0)).contains("insert into e_person_online (email, online_status, when_updated) values (?,?,?) on conflict (email) do update set online_status=excluded.online_status, when_updated=excluded.when_updated");
+ assertThat(sql.get(1)).contains("insert into e_person_online (email, online_status, when_updated) values (?,?,?) on conflict (email) do update set online_status=excluded.online_status, when_updated=excluded.when_updated");
+
+ List list = db.find(EPersonOnline.class).findList();
+ assertThat(list).hasSize(1);
+ assertThat(list.get(0).getWhenUpdated()).isEqualTo(bean2.getWhenUpdated());
+ }
+
+ @ForPlatform({Platform.POSTGRES, Platform.YUGABYTE})
+ @Test
+ void insertOnConflictUpdate_when_noIdValue() {
+ Database db = DB.getDefault();
+ db.truncate(EPersonOnline.class);
+ LoggedSql.start();
+
+ var bean = newBean("a@b.com");
+ db.insert(bean, onConflictDoUpdateAndGetGeneratedKeys);
+ assertThat(bean.getId()).isNotNull();
+
+ var bean2 = newBean("a@b.com");
+ bean2.setOnlineStatus(false);
+ db.insert(bean2, onConflictDoUpdateAndGetGeneratedKeys);
+ assertThat(bean2.getId()).isEqualTo(bean.getId());
+
+ var sql = LoggedSql.stop();
+ assertThat(sql).hasSize(2);
+ assertThat(sql.get(0)).contains("insert into e_person_online (email, online_status, when_updated) values (?,?,?) on conflict (email) do update set online_status=excluded.online_status, when_updated=excluded.when_updated");
+ assertThat(sql.get(1)).contains("insert into e_person_online (email, online_status, when_updated) values (?,?,?) on conflict (email) do update set online_status=excluded.online_status, when_updated=excluded.when_updated");
+
+ List list = db.find(EPersonOnline.class).findList();
+
+ assertThat(list).hasSize(1);
+ assertThat(list.get(0).getWhenUpdated()).isEqualTo(bean2.getWhenUpdated());
+ }
+
+ @ForPlatform({Platform.POSTGRES, Platform.YUGABYTE})
+ @Test
+ void insertOnConflictUpdate_when_idValueSupplied() {
+ Database db = DB.getDefault();
+ db.truncate(EPersonOnline.class);
+ LoggedSql.start();
+
+ var bean = newBean("a@b.com");
+ bean.setId(40_042L);
+ db.insert(bean, ON_CONFLICT_UPDATE);
+ assertThat(bean.getId()).isNotNull();
+
+ var bean2 = newBean("a@b.com");
+ bean2.setId(40_043L); // not expected but can be different
+ bean2.setOnlineStatus(false);
+
+ db.insert(bean2, onConflictDoUpdateAndGetGeneratedKeys);
+
+ var sql = LoggedSql.stop();
+ assertThat(sql).hasSize(2);
+ assertThat(sql.get(0)).contains("insert into e_person_online (id, email, online_status, when_updated) values (?,?,?,?) on conflict (email) do update set online_status=excluded.online_status, when_updated=excluded.when_updated");
+ assertThat(sql.get(1)).contains("insert into e_person_online (id, email, online_status, when_updated) values (?,?,?,?) on conflict (email) do update set online_status=excluded.online_status, when_updated=excluded.when_updated");
+
+ List list = db.find(EPersonOnline.class).findList();
+
+ assertThat(list).hasSize(1);
+ assertThat(list.get(0).getWhenUpdated()).isEqualTo(bean2.getWhenUpdated());
+ }
+
+ @ForPlatform({Platform.POSTGRES, Platform.YUGABYTE})
+ @Test
+ void insertAll_onConflictUpdate_when_noIdValue() {
+ Database db = DB.getDefault();
+ db.truncate(EPersonOnline.class);
+ LoggedSql.start();
+
+ var bean = newBean("a1@b.com");
+ var bean2 = newBean("a2@b.com");
+ var bean3 = newBean("a3@b.com");
+ db.insertAll(List.of(bean, bean2, bean3), ON_CONFLICT_UPDATE);
+
+ var sql = LoggedSql.stop();
+ assertThat(sql).hasSize(5);
+ assertThat(sql.get(0)).contains("insert into e_person_online (email, online_status, when_updated) values (?,?,?) on conflict (email) do update set online_status=excluded.online_status, when_updated=excluded.when_updated");
+ assertThat(sql.get(1)).contains(" -- bind");
+ assertThat(sql.get(2)).contains(" -- bind");
+ assertThat(sql.get(3)).contains(" -- bind");
+ assertThat(sql.get(4)).contains(" -- executeBatch()");
+
+ var bean4 = newBean("a1@b.com");
+ var bean5 = newBean("a5@b.com");
+ db.insertAll(List.of(bean4, bean5), ON_CONFLICT_UPDATE);
+
+ List list = db.find(EPersonOnline.class).orderBy("id").findList();
+ assertThat(list).hasSize(4);
+ }
+
+ @ForPlatform({Platform.POSTGRES, Platform.YUGABYTE})
+ @Test
+ void insertAll_onConflictUpdate_explicitTransaction() {
+ Database db = DB.getDefault();
+ db.truncate(EPersonOnline.class);
+ LoggedSql.start();
+
+ try (Transaction txn = DB.createTransaction()) {
+ txn.setBatchSize(3);
+ var bean = newBean("a1@b.com");
+ var bean2 = newBean("a2@b.com");
+ var bean3 = newBean("a3@b.com");
+ var bean4 = newBean("a4@b.com");
+ db.insertAll(List.of(bean, bean2, bean3, bean4), ON_CONFLICT_UPDATE, txn);
+ txn.commit();
+ }
+
+ var sql = LoggedSql.stop();
+ assertThat(sql).hasSize(8);
+ assertThat(sql.get(0)).contains("insert into e_person_online (email, online_status, when_updated) values (?,?,?) on conflict (email) do update set online_status=excluded.online_status, when_updated=excluded.when_updated");
+ assertThat(sql.get(1)).contains(" -- bind");
+ assertThat(sql.get(2)).contains(" -- bind");
+ assertThat(sql.get(3)).contains(" -- bind");
+ assertThat(sql.get(4)).contains(" -- executeBatch()");
+ assertThat(sql.get(5)).contains("insert into e_person_online (email, online_status, when_updated) values (?,?,?) on conflict (email) do update set online_status=excluded.online_status, when_updated=excluded.when_updated");
+ assertThat(sql.get(6)).contains(" -- bind");
+ assertThat(sql.get(7)).contains(" -- executeBatch()");
+
+ List list = db.find(EPersonOnline.class).orderBy("id").findList();
+ assertThat(list).hasSize(4);
+ }
+
+ @ForPlatform({Platform.POSTGRES, Platform.YUGABYTE})
+ @Test
+ void explicitConstraint() {
+ Database db = DB.getDefault();
+ db.truncate(EPersonOnline.class);
+ LoggedSql.start();
+
+ var bean = newBean("a1@b.com");
+ var bean2 = newBean("a2@b.com");
+ var bean3 = newBean("a3@b.com");
+
+ var ON_CONFLICT_ = InsertOptions.builder()
+ .onConflictUpdate()
+ .constraint("uq_e_person_online_email")
+ .build();
+
+ db.insertAll(List.of(bean, bean2, bean3), ON_CONFLICT_);
+
+ var sql = LoggedSql.stop();
+ assertThat(sql).hasSize(5);
+ assertThat(sql.get(0)).contains("insert into e_person_online (email, online_status, when_updated) values (?,?,?) on conflict on constraint uq_e_person_online_email do update set online_status=excluded.online_status, when_updated=excluded.when_updated");
+ assertThat(sql.get(1)).contains(" -- bind");
+ assertThat(sql.get(2)).contains(" -- bind");
+ assertThat(sql.get(3)).contains(" -- bind");
+ assertThat(sql.get(4)).contains(" -- executeBatch()");
+
+ List list = db.find(EPersonOnline.class).orderBy("id").findList();
+ assertThat(list).hasSize(3);
+ }
+
+ @ForPlatform({Platform.POSTGRES, Platform.YUGABYTE})
+ @Test
+ void explicitUniqueColumns() {
+ Database db = DB.getDefault();
+ db.truncate(EPersonOnline.class);
+ LoggedSql.start();
+
+ var bean = newBean("a1@b.com");
+ var bean2 = newBean("a2@b.com");
+ var bean3 = newBean("a3@b.com");
+
+ var ON_CONFLICT_ = InsertOptions.builder()
+ .onConflictUpdate()
+ .uniqueColumns(" email ")
+ .build();
+
+ db.insertAll(List.of(bean, bean2, bean3), ON_CONFLICT_);
+
+ var sql = LoggedSql.stop();
+ assertThat(sql).hasSize(5);
+ assertThat(sql.get(0)).contains("insert into e_person_online (email, online_status, when_updated) values (?,?,?) on conflict ( email ) do update set online_status=excluded.online_status, when_updated=excluded.when_updated");
+ assertThat(sql.get(1)).contains(" -- bind");
+ assertThat(sql.get(2)).contains(" -- bind");
+ assertThat(sql.get(3)).contains(" -- bind");
+ assertThat(sql.get(4)).contains(" -- executeBatch()");
+
+ List list = db.find(EPersonOnline.class).orderBy("id").findList();
+ assertThat(list).hasSize(3);
+ }
+
+ @ForPlatform({Platform.POSTGRES, Platform.YUGABYTE})
+ @Test
+ void explicitUpdateSet() {
+ Database db = DB.getDefault();
+ db.truncate(EPersonOnline.class);
+ LoggedSql.start();
+
+ var bean = newBean("a1@b.com");
+ var bean2 = newBean("a2@b.com");
+
+ var ON_CONFLICT_ = InsertOptions.builder()
+ .onConflictUpdate()
+ .updateSet("when_updated=excluded.when_updated, online_status=true")
+ .build();
+
+ db.insertAll(List.of(bean, bean2), ON_CONFLICT_);
+
+ var sql = LoggedSql.stop();
+ assertThat(sql).hasSize(4);
+ assertThat(sql.get(0)).contains("insert into e_person_online (email, online_status, when_updated) values (?,?,?) on conflict (email) do update set when_updated=excluded.when_updated, online_status=true");
+ assertThat(sql.get(1)).contains(" -- bind");
+ assertThat(sql.get(2)).contains(" -- bind");
+ assertThat(sql.get(3)).contains(" -- executeBatch()");
+
+ List list = db.find(EPersonOnline.class).orderBy("id").findList();
+ assertThat(list).hasSize(2);
+ }
+
+ @ForPlatform({Platform.POSTGRES, Platform.YUGABYTE})
+ @Test
+ void insertOnConflictNothing_when_noIdValue() {
+ Database db = DB.getDefault();
+ db.truncate(EPersonOnline.class);
+ LoggedSql.start();
+
+ var bean = newBean("a@b.com");
+ db.insert(bean, ON_CONFLICT_NOTHING);
+ assertThat(bean.getId()).isNotNull();
+
+ var bean2 = newBean("a@b.com");
+ bean2.setOnlineStatus(false);
+ db.insert(bean2, ON_CONFLICT_NOTHING);
+ assertThat(bean2.getId()).isNull();
+
+ var sql = LoggedSql.stop();
+ assertThat(sql).hasSize(2);
+ assertThat(sql.get(0)).contains("insert into e_person_online (email, online_status, when_updated) values (?,?,?) on conflict (email) do nothing");
+ assertThat(sql.get(1)).contains("insert into e_person_online (email, online_status, when_updated) values (?,?,?) on conflict (email) do nothing");
+
+ List list = db.find(EPersonOnline.class).findList();
+
+ assertThat(list).hasSize(1);
+ assertThat(list.get(0).getWhenUpdated()).isEqualTo(bean.getWhenUpdated());
+ }
+
+ private static EPersonOnline newBean(String email) {
+ EPersonOnline bean = new EPersonOnline();
+ bean.setEmail(email);
+ bean.setOnlineStatus(true);
+ return bean;
+ }
+}
diff --git a/ebean-test/src/test/java/org/tests/model/basic/Product.java b/ebean-test/src/test/java/org/tests/model/basic/Product.java
index df57dadd0..4f5557660 100644
--- a/ebean-test/src/test/java/org/tests/model/basic/Product.java
+++ b/ebean-test/src/test/java/org/tests/model/basic/Product.java
@@ -39,6 +39,13 @@ public class Product implements Serializable {
@Version
Timestamp updtime;
+ public Product(Integer id) {
+ this.id = id;
+ }
+
+ public Product() {
+ }
+
/**
* Return id.
*/
diff --git a/ebean-test/src/test/java/org/tests/model/orphanremoval/OmBeanListChild.java b/ebean-test/src/test/java/org/tests/model/orphanremoval/OmBeanListChild.java
index c4b07d3cd..7ed80c302 100644
--- a/ebean-test/src/test/java/org/tests/model/orphanremoval/OmBeanListChild.java
+++ b/ebean-test/src/test/java/org/tests/model/orphanremoval/OmBeanListChild.java
@@ -28,6 +28,11 @@ public class OmBeanListChild extends Model {
public Long getId() {
return id;
}
+
+ public OmBeanListChild setId(Long id) {
+ this.id = id;
+ return this;
+ }
}
diff --git a/ebean-test/src/test/java/org/tests/model/orphanremoval/OmBeanListParent.java b/ebean-test/src/test/java/org/tests/model/orphanremoval/OmBeanListParent.java
index 1aacb79d6..026199f70 100644
--- a/ebean-test/src/test/java/org/tests/model/orphanremoval/OmBeanListParent.java
+++ b/ebean-test/src/test/java/org/tests/model/orphanremoval/OmBeanListParent.java
@@ -2,10 +2,14 @@ package org.tests.model.orphanremoval;
import io.ebean.Model;
+import io.ebean.annotation.WhenCreated;
+import io.ebean.annotation.WhenModified;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Version;
+
+import java.time.Instant;
import java.util.List;
import static jakarta.persistence.CascadeType.ALL;
@@ -19,6 +23,13 @@ public class OmBeanListParent extends Model {
@Version
private long version;
+ private String name;
+
+ @WhenCreated
+ private Instant whenCreated;
+ @WhenModified
+ private Instant whenModified;
+
@OneToMany(cascade = ALL, mappedBy = "parent", orphanRemoval = true)
private List children;
@@ -35,6 +46,42 @@ public class OmBeanListParent extends Model {
this.children.clear();
this.children.addAll(children);
}
+
+ public void setChildren2(List children) {
+ this.children = children;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public long getVersion() {
+ return version;
+ }
+
+ public void setVersion(long version) {
+ this.version = version;
+ }
+
+ public Instant getWhenModified() {
+ return whenModified;
+ }
+
+ public void setWhenModified(Instant whenModified) {
+ this.whenModified = whenModified;
+ }
+
+ public Instant getWhenCreated() {
+ return whenCreated;
+ }
+
+ public void setWhenCreated(Instant whenCreated) {
+ this.whenCreated = whenCreated;
+ }
}
diff --git a/ebean-test/src/test/java/org/tests/model/orphanremoval/TestOrphanRemovalOverwrite.java b/ebean-test/src/test/java/org/tests/model/orphanremoval/TestOrphanRemovalOverwrite.java
index 446fff4b2..d03575e8c 100644
--- a/ebean-test/src/test/java/org/tests/model/orphanremoval/TestOrphanRemovalOverwrite.java
+++ b/ebean-test/src/test/java/org/tests/model/orphanremoval/TestOrphanRemovalOverwrite.java
@@ -1,17 +1,20 @@
package org.tests.model.orphanremoval;
+import io.ebean.DB;
+import io.ebean.test.LoggedSql;
import org.junit.jupiter.api.Test;
import java.util.List;
import static java.util.Collections.singletonList;
+import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
-public class TestOrphanRemovalOverwrite {
+class TestOrphanRemovalOverwrite {
@Test
- public void testOverwritingMapping() {
+ void testOverwritingMapping() {
OmBeanListParent parent = new OmBeanListParent();
parent.save();
@@ -29,8 +32,45 @@ public class TestOrphanRemovalOverwrite {
// Refreshing here generates new objects for the associated children that are referred to by the parent.
parent.refresh();
- assertNotNull(childList.get(0).getId());
- assertEquals(childList.get(0).getId(), parent.getChildren().get(0).getId());
+ OmBeanListChild child = childList.get(0);
+ assertNotNull(child.getId());
+
+ OmBeanListChild refreshedChild = parent.getChildren().get(0);
+ assertEquals(child.getId(), refreshedChild.getId());
+ assertEquals(child, refreshedChild);
assertEquals(childList, parent.getChildren());
}
+
+ @Test
+ void clearAddAll() {
+ OmBeanListChild c1 = new OmBeanListChild("c1");
+ OmBeanListChild c2 = new OmBeanListChild("c2");
+
+ OmBeanListParent parent = new OmBeanListParent();
+ parent.getChildren().add(c1);
+ parent.getChildren().add(c2);
+ parent.save();
+
+
+ OmBeanListChild c3 = new OmBeanListChild("c3");
+ c3.setId(c1.getId());
+
+ OmBeanListParent p1 = DB.find(OmBeanListParent.class, parent.getId());
+ List children = p1.getChildren();
+ children.clear();
+ children.add(c3);
+
+ LoggedSql.start();
+ DB.save(p1);
+
+ List sql = LoggedSql.stop();
+ assertThat(sql).hasSize(7);
+ assertThat(sql.get(0)).contains("delete from om_bean_list_child where id=?");
+ assertThat(sql.get(1)).contains(" -- bind");
+ assertThat(sql.get(2)).contains(" -- bind");
+ assertThat(sql.get(3)).contains(" -- executeBatch()");
+ assertThat(sql.get(4)).contains(" insert into om_bean_list_child");
+ assertThat(sql.get(5)).contains(" -- bind");
+ assertThat(sql.get(6)).contains(" -- executeBatch()");
+ }
}
diff --git a/ebean-test/src/test/java/org/tests/o2m/OneToManyListMarkAsDirtyTest.java b/ebean-test/src/test/java/org/tests/o2m/OneToManyListMarkAsDirtyTest.java
new file mode 100644
index 000000000..ae358b85d
--- /dev/null
+++ b/ebean-test/src/test/java/org/tests/o2m/OneToManyListMarkAsDirtyTest.java
@@ -0,0 +1,114 @@
+package org.tests.o2m;
+
+import io.ebean.DB;
+import io.ebean.xtest.BaseTestCase;
+import org.junit.jupiter.api.Test;
+import org.tests.model.orphanremoval.OmBeanListChild;
+import org.tests.model.orphanremoval.OmBeanListParent;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.LockSupport;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class OneToManyListMarkAsDirtyTest extends BaseTestCase {
+ @Test
+ void usingNewListWithNonDirtyParent_expect_orphanDeleted_works() {
+ // setup
+ var parent = new OmBeanListParent();
+ var a_b = new OmBeanListChild("b");
+ parent.getChildren().add(a_b);
+ DB.save(parent);
+ LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(1));
+
+ // act
+ var secondParent = DB.find(OmBeanListParent.class, parent.getId());
+ secondParent.setChildren2(new ArrayList<>()); //