Use composite primary key for HostHistory and ContactHistory (#809)

* Use composite primary key for HostHistory and ContactHistory

* Update flyway file version

* Make getters private

* Add javadoc

* Rebase on HEAD
This commit is contained in:
Shicong Huang 2020-10-01 11:01:57 -04:00 committed by GitHub
parent 71f86c9970
commit fd40a6a2b9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 248 additions and 89 deletions

View file

@ -17,8 +17,11 @@ package google.registry.model.contact;
import com.googlecode.objectify.Key; import com.googlecode.objectify.Key;
import com.googlecode.objectify.annotation.EntitySubclass; import com.googlecode.objectify.annotation.EntitySubclass;
import google.registry.model.EppResource; import google.registry.model.EppResource;
import google.registry.model.ImmutableObject;
import google.registry.model.contact.ContactHistory.ContactHistoryId;
import google.registry.model.reporting.HistoryEntry; import google.registry.model.reporting.HistoryEntry;
import google.registry.persistence.VKey; import google.registry.persistence.VKey;
import java.io.Serializable;
import java.util.Optional; import java.util.Optional;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.persistence.Access; import javax.persistence.Access;
@ -28,6 +31,7 @@ import javax.persistence.Entity;
import javax.persistence.GeneratedValue; import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType; import javax.persistence.GenerationType;
import javax.persistence.Id; import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.PostLoad; import javax.persistence.PostLoad;
/** /**
@ -47,13 +51,13 @@ import javax.persistence.PostLoad;
}) })
@EntitySubclass @EntitySubclass
@Access(AccessType.FIELD) @Access(AccessType.FIELD)
@IdClass(ContactHistoryId.class)
public class ContactHistory extends HistoryEntry { public class ContactHistory extends HistoryEntry {
// Store ContactBase instead of ContactResource so we don't pick up its @Id // Store ContactBase instead of ContactResource so we don't pick up its @Id
@Nullable ContactBase contactBase; @Nullable ContactBase contactBase;
@Column(nullable = false) @Id String contactRepoId;
VKey<ContactResource> contactRepoId;
@Id @Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "TempHistorySequenceGenerator") @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "TempHistorySequenceGenerator")
@ -76,7 +80,14 @@ public class ContactHistory extends HistoryEntry {
/** The key to the {@link ContactResource} this is based off of. */ /** The key to the {@link ContactResource} this is based off of. */
public VKey<ContactResource> getContactRepoId() { public VKey<ContactResource> getContactRepoId() {
return contactRepoId; return VKey.create(
ContactResource.class, contactRepoId, Key.create(ContactResource.class, contactRepoId));
}
/** Creates a {@link VKey} instance for this entity. */
public VKey<ContactHistory> createVKey() {
return VKey.create(
ContactHistory.class, new ContactHistoryId(contactRepoId, getId()), Key.create(this));
} }
@PostLoad @PostLoad
@ -87,10 +98,65 @@ public class ContactHistory extends HistoryEntry {
contactBase = null; contactBase = null;
} }
// Fill in the full, symmetric, parent repo ID key // Fill in the full, symmetric, parent repo ID key
Key<ContactResource> parentKey = parent = Key.create(ContactResource.class, contactRepoId);
Key.create(ContactResource.class, (String) contactRepoId.getSqlKey()); }
parent = parentKey;
contactRepoId = VKey.create(ContactResource.class, contactRepoId.getSqlKey(), parentKey); /** Class to represent the composite primary key of {@link ContactHistory} entity. */
static class ContactHistoryId extends ImmutableObject implements Serializable {
private String contactRepoId;
private Long id;
/** Hibernate requires this default constructor. */
private ContactHistoryId() {}
ContactHistoryId(String contactRepoId, long id) {
this.contactRepoId = contactRepoId;
this.id = id;
}
/**
* Returns the contact repository id.
*
* <p>This method is private because it is only used by Hibernate.
*/
@SuppressWarnings("unused")
private String getContactRepoId() {
return contactRepoId;
}
/**
* Returns the history revision id.
*
* <p>This method is private because it is only used by Hibernate.
*/
@SuppressWarnings("unused")
private long getId() {
return id;
}
/**
* Sets the contact repository id.
*
* <p>This method is private because it is only used by Hibernate and should not be used
* externally to keep immutability.
*/
@SuppressWarnings("unused")
private void setContactRepoId(String contactRepoId) {
this.contactRepoId = contactRepoId;
}
/**
* Sets the history revision id.
*
* <p>This method is private because it is only used by Hibernate and should not be used
* externally to keep immutability.
*/
@SuppressWarnings("unused")
private void setId(long id) {
this.id = id;
}
} }
@Override @Override
@ -111,9 +177,9 @@ public class ContactHistory extends HistoryEntry {
return this; return this;
} }
public Builder setContactRepoId(VKey<ContactResource> contactRepoId) { public Builder setContactRepoId(String contactRepoId) {
getInstance().contactRepoId = contactRepoId; getInstance().contactRepoId = contactRepoId;
contactRepoId.maybeGetOfyKey().ifPresent(parent -> getInstance().parent = parent); getInstance().parent = Key.create(ContactResource.class, contactRepoId);
return this; return this;
} }
@ -121,8 +187,7 @@ public class ContactHistory extends HistoryEntry {
@Override @Override
public Builder setParent(Key<? extends EppResource> parent) { public Builder setParent(Key<? extends EppResource> parent) {
super.setParent(parent); super.setParent(parent);
getInstance().contactRepoId = getInstance().contactRepoId = parent.getName();
VKey.create(ContactResource.class, parent.getName(), (Key<ContactResource>) parent);
return this; return this;
} }
} }

View file

@ -51,7 +51,6 @@ public class ContactResource extends ContactBase
@Override @Override
public VKey<ContactResource> createVKey() { public VKey<ContactResource> createVKey() {
// TODO(mmuller): create symmetric keys if we can ever reload both sides.
return VKey.create(ContactResource.class, getRepoId(), Key.create(this)); return VKey.create(ContactResource.class, getRepoId(), Key.create(this));
} }

View file

@ -156,8 +156,10 @@ public class DomainHistory extends HistoryEntry {
return VKey.create(DomainBase.class, domainRepoId, Key.create(DomainBase.class, domainRepoId)); return VKey.create(DomainBase.class, domainRepoId, Key.create(DomainBase.class, domainRepoId));
} }
/** Creates a {@link VKey} instance for this entity. */
public VKey<DomainHistory> createVKey() { public VKey<DomainHistory> createVKey() {
return VKey.createSql(DomainHistory.class, new DomainHistoryId(domainRepoId, getId())); return VKey.create(
DomainHistory.class, new DomainHistoryId(domainRepoId, getId()), Key.create(this));
} }
@PostLoad @PostLoad
@ -188,19 +190,45 @@ public class DomainHistory extends HistoryEntry {
this.id = id; this.id = id;
} }
String getDomainRepoId() { /**
* Returns the domain repository id.
*
* <p>This method is private because it is only used by Hibernate.
*/
@SuppressWarnings("unused")
private String getDomainRepoId() {
return domainRepoId; return domainRepoId;
} }
void setDomainRepoId(String domainRepoId) { /**
this.domainRepoId = domainRepoId; * Returns the history revision id.
} *
* <p>This method is private because it is only used by Hibernate.
long getId() { */
@SuppressWarnings("unused")
private long getId() {
return id; return id;
} }
void setId(long id) { /**
* Sets the domain repository id.
*
* <p>This method is private because it is only used by Hibernate and should not be used
* externally to keep immutability.
*/
@SuppressWarnings("unused")
private void setDomainRepoId(String domainRepoId) {
this.domainRepoId = domainRepoId;
}
/**
* Sets the history revision id.
*
* <p>This method is private because it is only used by Hibernate and should not be used
* externally to keep immutability.
*/
@SuppressWarnings("unused")
private void setId(long id) {
this.id = id; this.id = id;
} }
} }

View file

@ -14,11 +14,15 @@
package google.registry.model.host; package google.registry.model.host;
import com.googlecode.objectify.Key; import com.googlecode.objectify.Key;
import com.googlecode.objectify.annotation.EntitySubclass; import com.googlecode.objectify.annotation.EntitySubclass;
import google.registry.model.EppResource; import google.registry.model.EppResource;
import google.registry.model.ImmutableObject;
import google.registry.model.host.HostHistory.HostHistoryId;
import google.registry.model.reporting.HistoryEntry; import google.registry.model.reporting.HistoryEntry;
import google.registry.persistence.VKey; import google.registry.persistence.VKey;
import java.io.Serializable;
import java.util.Optional; import java.util.Optional;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.persistence.Access; import javax.persistence.Access;
@ -28,6 +32,7 @@ import javax.persistence.Entity;
import javax.persistence.GeneratedValue; import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType; import javax.persistence.GenerationType;
import javax.persistence.Id; import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.PostLoad; import javax.persistence.PostLoad;
/** /**
@ -48,13 +53,13 @@ import javax.persistence.PostLoad;
}) })
@EntitySubclass @EntitySubclass
@Access(AccessType.FIELD) @Access(AccessType.FIELD)
@IdClass(HostHistoryId.class)
public class HostHistory extends HistoryEntry { public class HostHistory extends HistoryEntry {
// Store HostBase instead of HostResource so we don't pick up its @Id // Store HostBase instead of HostResource so we don't pick up its @Id
@Nullable HostBase hostBase; @Nullable HostBase hostBase;
@Column(nullable = false) @Id String hostRepoId;
VKey<HostResource> hostRepoId;
@Id @Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "TempHistorySequenceGenerator") @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "TempHistorySequenceGenerator")
@ -77,7 +82,12 @@ public class HostHistory extends HistoryEntry {
/** The key to the {@link google.registry.model.host.HostResource} this is based off of. */ /** The key to the {@link google.registry.model.host.HostResource} this is based off of. */
public VKey<HostResource> getHostRepoId() { public VKey<HostResource> getHostRepoId() {
return hostRepoId; return VKey.create(HostResource.class, hostRepoId, Key.create(HostResource.class, hostRepoId));
}
/** Creates a {@link VKey} instance for this entity. */
public VKey<HostHistory> createVKey() {
return VKey.create(HostHistory.class, new HostHistoryId(hostRepoId, getId()), Key.create(this));
} }
@PostLoad @PostLoad
@ -88,9 +98,65 @@ public class HostHistory extends HistoryEntry {
hostBase = null; hostBase = null;
} }
// Fill in the full, symmetric, parent repo ID key // Fill in the full, symmetric, parent repo ID key
Key<HostResource> parentKey = Key.create(HostResource.class, (String) hostRepoId.getSqlKey()); parent = Key.create(HostResource.class, hostRepoId);
parent = parentKey; }
hostRepoId = VKey.create(HostResource.class, hostRepoId.getSqlKey(), parentKey);
/** Class to represent the composite primary key of {@link HostHistory} entity. */
static class HostHistoryId extends ImmutableObject implements Serializable {
private String hostRepoId;
private Long id;
/** Hibernate requires this default constructor. */
private HostHistoryId() {}
HostHistoryId(String hostRepoId, long id) {
this.hostRepoId = hostRepoId;
this.id = id;
}
/**
* Returns the host repository id.
*
* <p>This method is private because it is only used by Hibernate.
*/
@SuppressWarnings("unused")
private String getHostRepoId() {
return hostRepoId;
}
/**
* Returns the history revision id.
*
* <p>This method is private because it is only used by Hibernate.
*/
@SuppressWarnings("unused")
private long getId() {
return id;
}
/**
* Sets the host repository id.
*
* <p>This method is private because it is only used by Hibernate and should not be used
* externally to keep immutability.
*/
@SuppressWarnings("unused")
private void setHostRepoId(String hostRepoId) {
this.hostRepoId = hostRepoId;
}
/**
* Sets the history revision id.
*
* <p>This method is private because it is only used by Hibernate and should not be used
* externally to keep immutability.
*/
@SuppressWarnings("unused")
private void setId(long id) {
this.id = id;
}
} }
@Override @Override
@ -111,9 +177,9 @@ public class HostHistory extends HistoryEntry {
return this; return this;
} }
public Builder setHostRepoId(VKey<HostResource> hostRepoId) { public Builder setHostRepoId(String hostRepoId) {
getInstance().hostRepoId = hostRepoId; getInstance().hostRepoId = hostRepoId;
hostRepoId.maybeGetOfyKey().ifPresent(parent -> getInstance().parent = parent); getInstance().parent = Key.create(HostResource.class, hostRepoId);
return this; return this;
} }
@ -121,8 +187,7 @@ public class HostHistory extends HistoryEntry {
@Override @Override
public Builder setParent(Key<? extends EppResource> parent) { public Builder setParent(Key<? extends EppResource> parent) {
super.setParent(parent); super.setParent(parent);
getInstance().hostRepoId = getInstance().hostRepoId = parent.getName();
VKey.create(HostResource.class, parent.getName(), (Key<HostResource>) parent);
return this; return this;
} }
} }

View file

@ -308,19 +308,10 @@ public class HistoryEntry extends ImmutableObject implements Buildable, Datastor
new DomainHistory.Builder().copyFrom(this).setDomainRepoId(parent.getName()).build(); new DomainHistory.Builder().copyFrom(this).setDomainRepoId(parent.getName()).build();
} else if (parentKind.equals(getKind(HostResource.class))) { } else if (parentKind.equals(getKind(HostResource.class))) {
resultEntity = resultEntity =
new HostHistory.Builder() new HostHistory.Builder().copyFrom(this).setHostRepoId(parent.getName()).build();
.copyFrom(this)
.setHostRepoId(
VKey.create(HostResource.class, parent.getName(), (Key<HostResource>) parent))
.build();
} else if (parentKind.equals(getKind(ContactResource.class))) { } else if (parentKind.equals(getKind(ContactResource.class))) {
resultEntity = resultEntity =
new ContactHistory.Builder() new ContactHistory.Builder().copyFrom(this).setContactRepoId(parent.getName()).build();
.copyFrom(this)
.setContactRepoId(
VKey.create(
ContactResource.class, parent.getName(), (Key<ContactResource>) parent))
.build();
} else { } else {
throw new IllegalStateException( throw new IllegalStateException(
String.format("Unknown kind of HistoryEntry parent %s", parentKind)); String.format("Unknown kind of HistoryEntry parent %s", parentKind));

View file

@ -47,14 +47,13 @@ public class ContactHistoryTest extends EntityTestCase {
jpaTm().transact(() -> jpaTm().insert(contact)); jpaTm().transact(() -> jpaTm().insert(contact));
VKey<ContactResource> contactVKey = contact.createVKey(); VKey<ContactResource> contactVKey = contact.createVKey();
ContactResource contactFromDb = jpaTm().transact(() -> jpaTm().load(contactVKey)); ContactResource contactFromDb = jpaTm().transact(() -> jpaTm().load(contactVKey));
ContactHistory contactHistory = createContactHistory(contactFromDb, contactVKey); ContactHistory contactHistory = createContactHistory(contactFromDb, contact.getRepoId());
contactHistory.id = null; contactHistory.id = null;
jpaTm().transact(() -> jpaTm().insert(contactHistory)); jpaTm().transact(() -> jpaTm().insert(contactHistory));
jpaTm() jpaTm()
.transact( .transact(
() -> { () -> {
ContactHistory fromDatabase = ContactHistory fromDatabase = jpaTm().load(contactHistory.createVKey());
jpaTm().load(VKey.createSql(ContactHistory.class, contactHistory.getId()));
assertContactHistoriesEqual(fromDatabase, contactHistory); assertContactHistoriesEqual(fromDatabase, contactHistory);
assertThat(fromDatabase.getContactRepoId().getSqlKey()) assertThat(fromDatabase.getContactRepoId().getSqlKey())
.isEqualTo(contactHistory.getContactRepoId().getSqlKey()); .isEqualTo(contactHistory.getContactRepoId().getSqlKey());
@ -70,15 +69,17 @@ public class ContactHistoryTest extends EntityTestCase {
VKey<ContactResource> contactVKey = contact.createVKey(); VKey<ContactResource> contactVKey = contact.createVKey();
ContactResource contactFromDb = jpaTm().transact(() -> jpaTm().load(contactVKey)); ContactResource contactFromDb = jpaTm().transact(() -> jpaTm().load(contactVKey));
ContactHistory contactHistory = ContactHistory contactHistory =
createContactHistory(contactFromDb, contactVKey).asBuilder().setContactBase(null).build(); createContactHistory(contactFromDb, contact.getRepoId())
.asBuilder()
.setContactBase(null)
.build();
contactHistory.id = null; contactHistory.id = null;
jpaTm().transact(() -> jpaTm().insert(contactHistory)); jpaTm().transact(() -> jpaTm().insert(contactHistory));
jpaTm() jpaTm()
.transact( .transact(
() -> { () -> {
ContactHistory fromDatabase = ContactHistory fromDatabase = jpaTm().load(contactHistory.createVKey());
jpaTm().load(VKey.createSql(ContactHistory.class, contactHistory.getId()));
assertContactHistoriesEqual(fromDatabase, contactHistory); assertContactHistoriesEqual(fromDatabase, contactHistory);
assertThat(fromDatabase.getContactRepoId().getSqlKey()) assertThat(fromDatabase.getContactRepoId().getSqlKey())
.isEqualTo(contactHistory.getContactRepoId().getSqlKey()); .isEqualTo(contactHistory.getContactRepoId().getSqlKey());
@ -94,14 +95,13 @@ public class ContactHistoryTest extends EntityTestCase {
VKey<ContactResource> contactVKey = contact.createVKey(); VKey<ContactResource> contactVKey = contact.createVKey();
ContactResource contactFromDb = tm().transact(() -> tm().load(contactVKey)); ContactResource contactFromDb = tm().transact(() -> tm().load(contactVKey));
fakeClock.advanceOneMilli(); fakeClock.advanceOneMilli();
ContactHistory contactHistory = createContactHistory(contactFromDb, contactVKey); ContactHistory contactHistory = createContactHistory(contactFromDb, contact.getRepoId());
tm().transact(() -> tm().insert(contactHistory)); tm().transact(() -> tm().insert(contactHistory));
// retrieving a HistoryEntry or a ContactHistory with the same key should return the same object // retrieving a HistoryEntry or a ContactHistory with the same key should return the same object
// note: due to the @EntitySubclass annotation. all Keys for ContactHistory objects will have // note: due to the @EntitySubclass annotation. all Keys for ContactHistory objects will have
// type HistoryEntry // type HistoryEntry
VKey<ContactHistory> contactHistoryVKey = VKey<ContactHistory> contactHistoryVKey = contactHistory.createVKey();
VKey.createOfy(ContactHistory.class, Key.create(contactHistory));
VKey<HistoryEntry> historyEntryVKey = VKey<HistoryEntry> historyEntryVKey =
VKey.createOfy(HistoryEntry.class, Key.create(contactHistory.asHistoryEntry())); VKey.createOfy(HistoryEntry.class, Key.create(contactHistory.asHistoryEntry()));
ContactHistory hostHistoryFromDb = tm().transact(() -> tm().load(contactHistoryVKey)); ContactHistory hostHistoryFromDb = tm().transact(() -> tm().load(contactHistoryVKey));
@ -110,8 +110,7 @@ public class ContactHistoryTest extends EntityTestCase {
assertThat(hostHistoryFromDb).isEqualTo(historyEntryFromDb); assertThat(hostHistoryFromDb).isEqualTo(historyEntryFromDb);
} }
private ContactHistory createContactHistory( private ContactHistory createContactHistory(ContactBase contact, String contactRepoId) {
ContactBase contact, VKey<ContactResource> contactVKey) {
return new ContactHistory.Builder() return new ContactHistory.Builder()
.setType(HistoryEntry.Type.HOST_CREATE) .setType(HistoryEntry.Type.HOST_CREATE)
.setXmlBytes("<xml></xml>".getBytes(UTF_8)) .setXmlBytes("<xml></xml>".getBytes(UTF_8))
@ -122,14 +121,14 @@ public class ContactHistoryTest extends EntityTestCase {
.setReason("reason") .setReason("reason")
.setRequestedByRegistrar(true) .setRequestedByRegistrar(true)
.setContactBase(contact) .setContactBase(contact)
.setContactRepoId(contactVKey) .setContactRepoId(contactRepoId)
.build(); .build();
} }
static void assertContactHistoriesEqual(ContactHistory one, ContactHistory two) { static void assertContactHistoriesEqual(ContactHistory one, ContactHistory two) {
assertAboutImmutableObjects() assertAboutImmutableObjects()
.that(one) .that(one)
.isEqualExceptFields(two, "contactBase", "contactRepoId", "parent"); .isEqualExceptFields(two, "contactBase", "contactRepoId");
assertAboutImmutableObjects() assertAboutImmutableObjects()
.that(one.getContactBase().orElse(null)) .that(one.getContactBase().orElse(null))
.isEqualExceptFields(two.getContactBase().orElse(null), "repoId"); .isEqualExceptFields(two.getContactBase().orElse(null), "repoId");

View file

@ -121,8 +121,7 @@ public class DomainHistoryTest extends EntityTestCase {
// retrieving a HistoryEntry or a DomainHistory with the same key should return the same object // retrieving a HistoryEntry or a DomainHistory with the same key should return the same object
// note: due to the @EntitySubclass annotation. all Keys for DomainHistory objects will have // note: due to the @EntitySubclass annotation. all Keys for DomainHistory objects will have
// type HistoryEntry // type HistoryEntry
VKey<DomainHistory> domainHistoryVKey = VKey<DomainHistory> domainHistoryVKey = domainHistory.createVKey();
VKey.createOfy(DomainHistory.class, Key.create(domainHistory));
VKey<HistoryEntry> historyEntryVKey = VKey<HistoryEntry> historyEntryVKey =
VKey.createOfy(HistoryEntry.class, Key.create(domainHistory.asHistoryEntry())); VKey.createOfy(HistoryEntry.class, Key.create(domainHistory.asHistoryEntry()));
DomainHistory domainHistoryFromDb = tm().transact(() -> tm().load(domainHistoryVKey)); DomainHistory domainHistoryFromDb = tm().transact(() -> tm().load(domainHistoryVKey));
@ -155,7 +154,7 @@ public class DomainHistoryTest extends EntityTestCase {
assertAboutImmutableObjects() assertAboutImmutableObjects()
.that(one) .that(one)
.isEqualExceptFields( .isEqualExceptFields(
two, "domainContent", "domainRepoId", "parent", "nsHosts", "domainTransactionRecords"); two, "domainContent", "domainRepoId", "nsHosts", "domainTransactionRecords");
assertThat(one.getDomainContent().map(DomainContent::getDomainName)) assertThat(one.getDomainContent().map(DomainContent::getDomainName))
.isEqualTo(two.getDomainContent().map(DomainContent::getDomainName)); .isEqualTo(two.getDomainContent().map(DomainContent::getDomainName));
// NB: the record's ID gets reset by Hibernate, causing the hash code to differ so we have to // NB: the record's ID gets reset by Hibernate, causing the hash code to differ so we have to

View file

@ -48,14 +48,13 @@ public class HostHistoryTest extends EntityTestCase {
VKey<HostResource> hostVKey = VKey<HostResource> hostVKey =
VKey.create(HostResource.class, "host1", Key.create(HostResource.class, "host1")); VKey.create(HostResource.class, "host1", Key.create(HostResource.class, "host1"));
HostResource hostFromDb = jpaTm().transact(() -> jpaTm().load(hostVKey)); HostResource hostFromDb = jpaTm().transact(() -> jpaTm().load(hostVKey));
HostHistory hostHistory = createHostHistory(hostFromDb, hostVKey); HostHistory hostHistory = createHostHistory(hostFromDb, host.getRepoId());
hostHistory.id = null; hostHistory.id = null;
jpaTm().transact(() -> jpaTm().insert(hostHistory)); jpaTm().transact(() -> jpaTm().insert(hostHistory));
jpaTm() jpaTm()
.transact( .transact(
() -> { () -> {
HostHistory fromDatabase = HostHistory fromDatabase = jpaTm().load(hostHistory.createVKey());
jpaTm().load(VKey.createSql(HostHistory.class, hostHistory.getId()));
assertHostHistoriesEqual(fromDatabase, hostHistory); assertHostHistoriesEqual(fromDatabase, hostHistory);
assertThat(fromDatabase.getHostRepoId().getSqlKey()) assertThat(fromDatabase.getHostRepoId().getSqlKey())
.isEqualTo(hostHistory.getHostRepoId().getSqlKey()); .isEqualTo(hostHistory.getHostRepoId().getSqlKey());
@ -68,20 +67,16 @@ public class HostHistoryTest extends EntityTestCase {
HostResource host = newHostResourceWithRoid("ns1.example.com", "host1"); HostResource host = newHostResourceWithRoid("ns1.example.com", "host1");
jpaTm().transact(() -> jpaTm().insert(host)); jpaTm().transact(() -> jpaTm().insert(host));
VKey<HostResource> hostVKey = HostResource hostFromDb = jpaTm().transact(() -> jpaTm().load(host.createVKey()));
VKey.create(HostResource.class, "host1", Key.create(HostResource.class, "host1"));
HostResource hostFromDb = jpaTm().transact(() -> jpaTm().load(hostVKey));
HostHistory hostHistory = HostHistory hostHistory =
createHostHistory(hostFromDb, hostVKey).asBuilder().setHostBase(null).build(); createHostHistory(hostFromDb, host.getRepoId()).asBuilder().setHostBase(null).build();
hostHistory.id = null; hostHistory.id = null;
jpaTm().transact(() -> jpaTm().insert(hostHistory)); jpaTm().transact(() -> jpaTm().insert(hostHistory));
jpaTm() jpaTm()
.transact( .transact(
() -> { () -> {
HostHistory fromDatabase = HostHistory fromDatabase = jpaTm().load(hostHistory.createVKey());
jpaTm().load(VKey.createSql(HostHistory.class, hostHistory.getId()));
assertHostHistoriesEqual(fromDatabase, hostHistory); assertHostHistoriesEqual(fromDatabase, hostHistory);
assertThat(fromDatabase.getHostRepoId().getSqlKey()) assertThat(fromDatabase.getHostRepoId().getSqlKey())
.isEqualTo(hostHistory.getHostRepoId().getSqlKey()); .isEqualTo(hostHistory.getHostRepoId().getSqlKey());
@ -97,14 +92,14 @@ public class HostHistoryTest extends EntityTestCase {
VKey<HostResource> hostVKey = VKey<HostResource> hostVKey =
VKey.create(HostResource.class, "host1", Key.create(HostResource.class, "host1")); VKey.create(HostResource.class, "host1", Key.create(HostResource.class, "host1"));
HostResource hostFromDb = tm().transact(() -> tm().load(hostVKey)); HostResource hostFromDb = tm().transact(() -> tm().load(hostVKey));
HostHistory hostHistory = createHostHistory(hostFromDb, hostVKey); HostHistory hostHistory = createHostHistory(hostFromDb, host.getRepoId());
fakeClock.advanceOneMilli(); fakeClock.advanceOneMilli();
tm().transact(() -> tm().insert(hostHistory)); tm().transact(() -> tm().insert(hostHistory));
// retrieving a HistoryEntry or a HostHistory with the same key should return the same object // retrieving a HistoryEntry or a HostHistory with the same key should return the same object
// note: due to the @EntitySubclass annotation. all Keys for HostHistory objects will have // note: due to the @EntitySubclass annotation. all Keys for HostHistory objects will have
// type HistoryEntry // type HistoryEntry
VKey<HostHistory> hostHistoryVKey = VKey.createOfy(HostHistory.class, Key.create(hostHistory)); VKey<HostHistory> hostHistoryVKey = hostHistory.createVKey();
VKey<HistoryEntry> historyEntryVKey = VKey<HistoryEntry> historyEntryVKey =
VKey.createOfy(HistoryEntry.class, Key.create(hostHistory.asHistoryEntry())); VKey.createOfy(HistoryEntry.class, Key.create(hostHistory.asHistoryEntry()));
HostHistory hostHistoryFromDb = tm().transact(() -> tm().load(hostHistoryVKey)); HostHistory hostHistoryFromDb = tm().transact(() -> tm().load(hostHistoryVKey));
@ -120,7 +115,7 @@ public class HostHistoryTest extends EntityTestCase {
.isEqualExceptFields(two.getHostBase().orElse(null), "repoId"); .isEqualExceptFields(two.getHostBase().orElse(null), "repoId");
} }
private HostHistory createHostHistory(HostBase hostBase, VKey<HostResource> hostVKey) { private HostHistory createHostHistory(HostBase hostBase, String hostRepoId) {
return new HostHistory.Builder() return new HostHistory.Builder()
.setType(HistoryEntry.Type.HOST_CREATE) .setType(HistoryEntry.Type.HOST_CREATE)
.setXmlBytes("<xml></xml>".getBytes(UTF_8)) .setXmlBytes("<xml></xml>".getBytes(UTF_8))
@ -131,7 +126,7 @@ public class HostHistoryTest extends EntityTestCase {
.setReason("reason") .setReason("reason")
.setRequestedByRegistrar(true) .setRequestedByRegistrar(true)
.setHostBase(hostBase) .setHostBase(hostBase)
.setHostRepoId(hostVKey) .setHostRepoId(hostRepoId)
.build(); .build();
} }
} }

View file

@ -88,11 +88,7 @@ public class LegacyHistoryObjectTest extends EntityTestCase {
jpaTm().insert(legacyContactHistory); jpaTm().insert(legacyContactHistory);
}); });
ContactHistory legacyHistoryFromSql = ContactHistory legacyHistoryFromSql =
jpaTm() jpaTm().transact(() -> jpaTm().load(legacyContactHistory.createVKey()));
.transact(
() ->
jpaTm()
.load(VKey.createSql(ContactHistory.class, legacyContactHistory.getId())));
assertAboutImmutableObjects() assertAboutImmutableObjects()
.that(legacyContactHistory) .that(legacyContactHistory)
.isEqualExceptFields(legacyHistoryFromSql); .isEqualExceptFields(legacyHistoryFromSql);
@ -172,9 +168,7 @@ public class LegacyHistoryObjectTest extends EntityTestCase {
jpaTm().insert(legacyHostHistory); jpaTm().insert(legacyHostHistory);
}); });
HostHistory legacyHistoryFromSql = HostHistory legacyHistoryFromSql =
jpaTm() jpaTm().transact(() -> jpaTm().load(legacyHostHistory.createVKey()));
.transact(
() -> jpaTm().load(VKey.createSql(HostHistory.class, legacyHostHistory.getId())));
assertAboutImmutableObjects().that(legacyHostHistory).isEqualExceptFields(legacyHistoryFromSql); assertAboutImmutableObjects().that(legacyHostHistory).isEqualExceptFields(legacyHistoryFromSql);
// can't compare hostRepoId directly since it doesn't save the ofy key in SQL // can't compare hostRepoId directly since it doesn't save the ofy key in SQL
assertThat(legacyHostHistory.getHostRepoId().getSqlKey()) assertThat(legacyHostHistory.getHostRepoId().getSqlKey())

View file

@ -131,9 +131,9 @@ class google.registry.model.contact.ContactHistory {
google.registry.model.domain.Period period; google.registry.model.domain.Period period;
google.registry.model.eppcommon.Trid trid; google.registry.model.eppcommon.Trid trid;
google.registry.model.reporting.HistoryEntry$Type type; google.registry.model.reporting.HistoryEntry$Type type;
google.registry.persistence.VKey<google.registry.model.contact.ContactResource> contactRepoId;
java.lang.Boolean requestedByRegistrar; java.lang.Boolean requestedByRegistrar;
java.lang.String clientId; java.lang.String clientId;
java.lang.String contactRepoId;
java.lang.String otherClientId; java.lang.String otherClientId;
java.lang.String reason; java.lang.String reason;
java.util.Set<google.registry.model.reporting.DomainTransactionRecord> domainTransactionRecords; java.util.Set<google.registry.model.reporting.DomainTransactionRecord> domainTransactionRecords;
@ -401,9 +401,9 @@ class google.registry.model.host.HostHistory {
google.registry.model.eppcommon.Trid trid; google.registry.model.eppcommon.Trid trid;
google.registry.model.host.HostBase hostBase; google.registry.model.host.HostBase hostBase;
google.registry.model.reporting.HistoryEntry$Type type; google.registry.model.reporting.HistoryEntry$Type type;
google.registry.persistence.VKey<google.registry.model.host.HostResource> hostRepoId;
java.lang.Boolean requestedByRegistrar; java.lang.Boolean requestedByRegistrar;
java.lang.String clientId; java.lang.String clientId;
java.lang.String hostRepoId;
java.lang.String otherClientId; java.lang.String otherClientId;
java.lang.String reason; java.lang.String reason;
java.util.Set<google.registry.model.reporting.DomainTransactionRecord> domainTransactionRecords; java.util.Set<google.registry.model.reporting.DomainTransactionRecord> domainTransactionRecords;

View file

@ -56,3 +56,4 @@ V55__domain_history_fields.sql
V56__rename_host_table.sql V56__rename_host_table.sql
V57__history_null_content.sql V57__history_null_content.sql
V58__drop_default_value_and_sequences_for_billing_event.sql V58__drop_default_value_and_sequences_for_billing_event.sql
V59__use_composite_primary_key_for_contact_and_host_history_table.sql

View file

@ -0,0 +1,23 @@
-- Copyright 2020 The Nomulus Authors. All Rights Reserved.
--
-- Licensed 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
--
-- http://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.
alter table "ContactHistory" drop constraint "ContactHistory_pkey";
alter table "ContactHistory"
add constraint "ContactHistory_pkey" primary key (contact_repo_id, history_revision_id);
alter table "HostHistory" drop constraint "HostHistory_pkey";
alter table "HostHistory"
add constraint "HostHistory_pkey" primary key (host_repo_id, history_revision_id);

View file

@ -151,6 +151,7 @@ create sequence temp_history_id_sequence start 1 increment 50;
); );
create table "ContactHistory" ( create table "ContactHistory" (
contact_repo_id text not null,
history_revision_id int8 not null, history_revision_id int8 not null,
history_by_superuser boolean not null, history_by_superuser boolean not null,
history_registrar_id text, history_registrar_id text,
@ -215,8 +216,7 @@ create sequence temp_history_id_sequence start 1 increment 50;
last_epp_update_time timestamptz, last_epp_update_time timestamptz,
statuses text[], statuses text[],
update_timestamp timestamptz, update_timestamp timestamptz,
contact_repo_id text not null, primary key (contact_repo_id, history_revision_id)
primary key (history_revision_id)
); );
create table "Cursor" ( create table "Cursor" (
@ -400,6 +400,7 @@ create sequence temp_history_id_sequence start 1 increment 50;
); );
create table "HostHistory" ( create table "HostHistory" (
host_repo_id text not null,
history_revision_id int8 not null, history_revision_id int8 not null,
history_by_superuser boolean not null, history_by_superuser boolean not null,
history_registrar_id text, history_registrar_id text,
@ -423,8 +424,7 @@ create sequence temp_history_id_sequence start 1 increment 50;
last_epp_update_time timestamptz, last_epp_update_time timestamptz,
statuses text[], statuses text[],
update_timestamp timestamptz, update_timestamp timestamptz,
host_repo_id text not null, primary key (host_repo_id, history_revision_id)
primary key (history_revision_id)
); );
create table "Lock" ( create table "Lock" (

View file

@ -1084,7 +1084,7 @@ ALTER TABLE ONLY public."ClaimsList"
-- --
ALTER TABLE ONLY public."ContactHistory" ALTER TABLE ONLY public."ContactHistory"
ADD CONSTRAINT "ContactHistory_pkey" PRIMARY KEY (history_revision_id); ADD CONSTRAINT "ContactHistory_pkey" PRIMARY KEY (contact_repo_id, history_revision_id);
-- --
@ -1140,7 +1140,7 @@ ALTER TABLE ONLY public."GracePeriod"
-- --
ALTER TABLE ONLY public."HostHistory" ALTER TABLE ONLY public."HostHistory"
ADD CONSTRAINT "HostHistory_pkey" PRIMARY KEY (history_revision_id); ADD CONSTRAINT "HostHistory_pkey" PRIMARY KEY (host_repo_id, history_revision_id);
-- --