package org.tests.merge; import io.ebean.BaseTestCase; import io.ebean.Ebean; import io.ebean.FetchPath; import io.ebean.MergeOptions; import io.ebean.MergeOptionsBuilder; import io.ebean.text.PathProperties; import org.ebeantest.LoggedSqlCollector; import org.junit.Test; import java.util.ArrayList; import java.util.List; import java.util.Random; import java.util.UUID; import static org.assertj.core.api.Assertions.assertThat; public class TestMergeCustomer extends BaseTestCase { private Random random = new Random(); @Test public void customerOnly_defaultOptions_expect_updateOnly() { MCustomer mCustomer = partial("cust1", "(id,name,version)"); mCustomer.setName("NotCust0"); LoggedSqlCollector.start(); Ebean.merge(mCustomer); List sql = LoggedSqlCollector.stop(); assertThat(sql).hasSize(1); assertSql(sql.get(0)).contains("update mcustomer set name=?, version=? where id=? and version=?"); } /** * So this is effectively the same as a stateless update. */ @Test public void customerOnly_expect_updateOnly() { MCustomer mCustomer = partial("cust1", "(id,name,version)"); mCustomer.setName("NotCust1"); MergeOptions options = new MergeOptionsBuilder().build(); LoggedSqlCollector.start(); Ebean.merge(mCustomer, options); List sql = LoggedSqlCollector.stop(); assertThat(sql).hasSize(1); assertSql(sql.get(0)).contains("update mcustomer set name=?, version=? where id=? and version=?"); } @Test public void customerOnly_setClientGeneratedIds_expect_selectAndUpdate() { MCustomer mCustomer = partial("cust2", "(id,name,version)"); mCustomer.setName("NotCust2"); MergeOptions options = new MergeOptionsBuilder().setClientGeneratedIds().build(); LoggedSqlCollector.start(); server().merge(mCustomer, options); List sql = LoggedSqlCollector.stop(); assertThat(sql).hasSize(2); assertSql(sql.get(0)).contains("select t0.id from mcustomer t0 where t0.id = ?"); assertSql(sql.get(1)).contains("update mcustomer set name=?, version=? where id=? and version=?"); } @Test public void customerWithAddresses_setClientGeneratedIds_expect_selectAndUpdate() { MCustomer mCustomer = partial("cust3", "(id,name,version,shippingAddress(*),billingAddress(*))"); mCustomer.setName("NotCust3"); mCustomer.getBillingAddress().setStreet("modBillStreet"); mCustomer.getShippingAddress().setCity("modShipCity"); MergeOptions options = new MergeOptionsBuilder() .addPath("shippingAddress") .addPath("billingAddress") .setClientGeneratedIds() .build(); LoggedSqlCollector.start(); server().merge(mCustomer, options); List sql = LoggedSqlCollector.stop(); assertThat(sql).hasSize(5); assertSql(sql.get(0)).contains("select t0.id, t2.id, t1.id from mcustomer t0 left join maddress t2 on t2.id = t0.shipping_address_id left join maddress t1 on t1.id = t0.billing_address_id where t0.id = ?"); assertSql(sql.get(1)).contains("update maddress set street=?, city=?, version=? where id=? and version=?"); assertSqlBind(sql, 2, 3); assertThat(sql.get(4)).contains("update mcustomer set name=?, version=?, shipping_address_id=?, billing_address_id=? where id=? and version=?"); } @Test public void customerWithAddresses_newAddress_setClientGeneratedIds_expect_insertAddress() { MCustomer mCustomer = partial("cust3", "(id,name,version,shippingAddress(*),billingAddress(*))"); mCustomer.setName("NotCust3"); // new billing address - no Id value so must be insert mCustomer.setBillingAddress(new MAddress("Short", "Mid Wicket")); mCustomer.getShippingAddress().setCity("modShipCity"); MergeOptions options = new MergeOptionsBuilder() .addPath("shippingAddress") .addPath("billingAddress") .setClientGeneratedIds() .build(); LoggedSqlCollector.start(); server().merge(mCustomer, options); List sql = LoggedSqlCollector.stop(); assertThat(sql).hasSize(6); assertSql(sql.get(0)).contains("select t0.id, t2.id, t1.id from mcustomer t0 left join maddress t2 on t2.id = t0.shipping_address_id left join maddress t1 on t1.id = t0.billing_address_id where t0.id = ?"); assertSql(sql.get(1)).contains("insert into maddress (id, street, city, version) values (?,?,?,?)"); assertSqlBind(sql.get(2)); assertThat(sql.get(3)).contains("update maddress set street=?, city=?, version=? where id=? and version=?"); assertSqlBind(sql.get(4)); assertThat(sql.get(5)).contains("update mcustomer set name=?, version=?, shipping_address_id=?, billing_address_id=? where id=? and version=?"); } @Test public void customerWithAddresses_newAddressWithId_setClientGeneratedIds_expect_additionalCheckForAddressInsert() { MCustomer mCustomer = partial("cust3", "(id,name,version,shippingAddress(*),billingAddress(*))"); mCustomer.setName("NotCust3"); // new billing address - has Id value + setClientGeneratedIds ... so extra query to check MAddress mAddress = new MAddress("Short", "Mid Wicket"); mAddress.setId(UUID.randomUUID()); mCustomer.setBillingAddress(mAddress); mCustomer.getShippingAddress().setCity("modShipCity"); MergeOptions options = new MergeOptionsBuilder() .addPath("shippingAddress") .addPath("billingAddress") .setClientGeneratedIds() // As we are using clientIds ... we don't know if the new UUID is an insert or update without checking .build(); LoggedSqlCollector.start(); server().merge(mCustomer, options); List sql = LoggedSqlCollector.stop(); assertThat(sql).hasSize(7); assertSql(sql.get(0)).contains("select t0.id, t2.id, t1.id from mcustomer t0 left join maddress t2 on t2.id = t0.shipping_address_id left join maddress t1 on t1.id = t0.billing_address_id where t0.id = ?"); // Additional check to see if the address with the unknown UUID is 'insert' or 'update' assertSql(sql.get(1)).contains("select t0.id from maddress t0 where t0.id = ?"); assertSql(sql.get(2)).contains("insert into maddress (id, street, city, version) values (?,?,?,?)"); assertSqlBind(sql.get(3)); assertThat(sql.get(4)).contains("update maddress set street=?, city=?, version=? where id=? and version=?"); assertSqlBind(sql.get(5)); assertThat(sql.get(6)).contains("update mcustomer set name=?, version=?, shipping_address_id=?, billing_address_id=? where id=? and version=?"); } @Test public void assocOne_onlineIsNull() { MCustomer c = new MCustomer("Null Address"); c.setBillingAddress(new MAddress("Cow corner", "Mid Wicket")); Ebean.save(c); MCustomer mCustomer = rebuildViaJson(c); MAddress mAddress = new MAddress("Silly", "Mid Wicket"); mAddress.setId(UUID.randomUUID()); mCustomer.setShippingAddress(mAddress); MergeOptions options = new MergeOptionsBuilder() .addPath("shippingAddress") .addPath("billingAddress") .setClientGeneratedIds() .build(); Ebean.merge(mCustomer, options); } private MCustomer rebuildViaJson(MCustomer input) { String asJson = Ebean.json().toJson(input); return Ebean.json().toBean(MCustomer.class, asJson); } @Test public void whenContacts_isNull_expect_deleteContacts() { MCustomer mCustomer = partial("cust6", "(id,name,version,shippingAddress(id),billingAddress(id),contacts(*))"); // null contacts ... but in path so this means - delete all contacts mCustomer.setContacts(null); MergeOptions options = new MergeOptionsBuilder() .addPath("contacts") .build(); LoggedSqlCollector.start(); server().merge(mCustomer, options); List sql = LoggedSqlCollector.stop(); if (isPersistBatchOnCascade()) { assertThat(sql).hasSize(20); } assertSql(sql.get(0)).contains("select t0.id, t1.id from mcustomer t0 left join mcontact t1 on t1.customer_id = t0.id where t0.id = ?"); assertSql(sql.get(1)).contains("delete from mcontact_message where contact_id = ?"); assertThat(sql.get(3)).contains("delete from mcontact where id=?"); assertThat(sql.get(19)).contains("update mcustomer set name=?, version=?, shipping_address_id=?, billing_address_id=? where id=? and version=?"); } @Test public void whenContacts_isEmpty_expect_deleteContacts() { MCustomer mCustomer = partial("cust6", "(id,name,version,shippingAddress(id),billingAddress(id),contacts(*))"); // empty contacts ... but in path so this means - delete all contacts mCustomer.setContacts(new ArrayList<>()); MergeOptions options = new MergeOptionsBuilder() .addPath("contacts") .build(); LoggedSqlCollector.start(); server().merge(mCustomer, options); List sql = LoggedSqlCollector.stop(); if (isPersistBatchOnCascade()) { assertThat(sql).hasSize(20); } assertSql(sql.get(0)).contains("select t0.id, t1.id from mcustomer t0 left join mcontact t1 on t1.customer_id = t0.id where t0.id = ?"); assertSql(sql.get(1)).contains("delete from mcontact_message where contact_id = ?"); assertThat(sql.get(3)).contains("delete from mcontact where id=?"); assertThat(sql.get(19)).contains("update mcustomer set name=?, version=?, shipping_address_id=?, billing_address_id=? where id=? and version=?"); } @Test public void whenContacts_mixed_expect_deleteInsertUpdateContacts() { MCustomer mCustomer = partial("cust6", "(id,name,version,shippingAddress(id),billingAddress(id),contacts(*))"); List contacts = mCustomer.getContacts(); MContact mContact = new MContact("z@a.com", "z", "zed"); mContact.setId(UUID.randomUUID()); contacts.add(mContact); contacts.get(0).setEmail("a@beta.com"); contacts.get(1).setEmail("b@beta.com"); contacts.remove(4); contacts.remove(2); MContact mContactEnd = new MContact("z@z.com", "zx", "zedXtra"); mContactEnd.setId(UUID.randomUUID()); contacts.add(mContactEnd); MergeOptions options = new MergeOptionsBuilder() .addPath("contacts") .build(); LoggedSqlCollector.start(); server().merge(mCustomer, options); List sql = LoggedSqlCollector.stop(); if (isPersistBatchOnCascade()) { assertThat(sql).hasSize(16); assertSql(sql.get(0)).contains("select t0.id, t1.id from mcustomer t0 left join mcontact t1 on t1.customer_id = t0.id where t0.id = ?"); assertSql(sql.get(1)).contains("delete from mcontact_message where contact_id = ?"); assertThat(sql.get(3)).contains("delete from mcontact where id=?"); assertThat(sql.get(7)).contains("update mcustomer set name=?, version=?, shipping_address_id=?, billing_address_id=? where id=? and version=?"); } if (isPersistBatchOnCascade()) { assertThat(sql.get(8)).contains("insert into mcontact"); assertThat(sql.get(9)).contains("-- bind("); assertThat(sql.get(11)).contains("update mcontact set email=?, first_name=?, last_name=?, version=?, customer_id=? where id=? and version=?"); } else { assertThat(sql.get(6)).contains("update mcontact set email=?, first_name=?, last_name=?, version=?, customer_id=? where id=? and version=?"); assertThat(sql.get(7)).contains("update mcontact set email=?, first_name=?, last_name=?, version=?, customer_id=? where id=? and version=?"); assertThat(sql.get(10)).contains("insert into mcontact"); assertThat(sql.get(11)).contains("insert into mcontact"); } } @Test public void fullMonty() { MCustomer cust1 = customer("monty1"); modify(cust1); MergeOptions options = new MergeOptionsBuilder() .addPath("billingAddress") .addPath("shippingAddress") .addPath("contacts") .addPath("contacts.messages") .setClientGeneratedIds() .setDeletePermanent() .build(); LoggedSqlCollector.start(); server().merge(cust1, options); List sql = LoggedSqlCollector.stop(); if (isPersistBatchOnCascade()) { assertSql(sql.get(0)).contains("select t0.id, t3.id, t1.id, t2.id from mcustomer t0 left join maddress t3 on t3.id = t0.shipping_address_id left join maddress t1 on t1.id = t0.billing_address_id left join mcontact t2 on t2.customer_id = t0.id where t0.id = ?"); if (isH2() || isHana()) { // with nested OneToMany .. we need a second query to read the contact message ids assertSql(sql.get(1)).contains("select t0.contact_id, t0.id from mcontact_message t0 where (t0.contact_id) in (?,?,?,?,?,?,?,?,?,?)"); } assertSql(sql.get(2)).contains("delete from mcontact_message where contact_id = ?"); assertThat(sql.get(4)).contains("delete from mcontact where id=?"); assertThat(sql.get(5)).contains("delete from mcontact_message where contact_id = ?"); assertThat(sql.get(7)).contains("delete from mcontact where id=?"); assertThat(sql.get(8)).contains("update maddress set street=?, city=?, version=? where id=? and version=?"); assertThat(sql.get(13)).contains("update mcontact set email=?, first_name=?, last_name=?, version=?, customer_id=? where id=? and version=?"); assertSqlBind(sql, 14, 17); assertThat(sql.get(18)).contains("update mcontact_message set title=?, subject=?, notes=?, version=?, contact_id=? where id=? and version=?"); assertSqlBind(sql, 19, 22); } } private void modify(MCustomer cust) { List contacts = cust.getContacts(); MContact mContact = new MContact("z@a.com", "z", "zed"); mContact.setId(UUID.randomUUID()); contacts.add(mContact); contacts.get(0).setEmail("a@beta.com"); contacts.get(1).setEmail("b@beta.com"); contacts.remove(4); contacts.remove(2); cust.setName(cust.getName()+" modified"); cust.getBillingAddress().setStreet("Short"); MAddress mAddress = new MAddress("Broken", "Dreams"); mAddress.setId(UUID.randomUUID()); cust.setShippingAddress(null); } private MCustomer partial(String name, String fetchGraph) { MCustomer cust1 = buildCustomer(name); Ebean.save(cust1); FetchPath fetchPath = PathProperties.parse(fetchGraph); String asJson = Ebean.json().toJson(cust1, fetchPath); return Ebean.json().toBean(MCustomer.class, asJson); } private MCustomer customer(String name) { MCustomer cust1 = buildCustomer(name); Ebean.save(cust1); String asJson = Ebean.json().toJson(cust1); return Ebean.json().toBean(MCustomer.class, asJson); } private MCustomer buildCustomer(String name) { MCustomer c = new MCustomer(name); c.setShippingAddress(new MAddress("Fleet st", "London")); c.setBillingAddress(new MAddress("Cow corner", "Mid Wicket")); c.getContacts().add(addContact("a@a.com", "a", "alligator")); c.getContacts().add(addContact("b@a.com", "b", "beaver")); c.getContacts().add(addContact("c@a.com", "c", "crow")); c.getContacts().add(addContact("d@a.com", "d", "dog")); c.getContacts().add(addContact("e@a.com", "e", "ent")); c.getContacts().add(addContact("f@a.com", "f", "frog")); return c; } private MContact addContact(String email, String first, String last) { MContact mContact = new MContact(email, first, last); int i = 1 + random.nextInt(2); for (int j = 0; j < i; j++) { mContact.getMessages().add(new MContactMessage(first+" "+i, last+" "+i)); } return mContact; } }