diff --git a/core/src/main/java/google/registry/model/EppResource.java b/core/src/main/java/google/registry/model/EppResource.java
index 81531b37c..364a76738 100644
--- a/core/src/main/java/google/registry/model/EppResource.java
+++ b/core/src/main/java/google/registry/model/EppResource.java
@@ -48,6 +48,8 @@ import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.stream.StreamSupport;
+import javax.persistence.Access;
+import javax.persistence.AccessType;
import javax.persistence.Column;
import javax.persistence.MappedSuperclass;
import javax.persistence.Transient;
@@ -56,15 +58,21 @@ import org.joda.time.Duration;
/** An EPP entity object (i.e. a domain, contact, or host). */
@MappedSuperclass
+@Access(AccessType.FIELD) // otherwise it'll use the default if the repoId (property)
public abstract class EppResource extends BackupGroupRoot implements Buildable {
/**
* Unique identifier in the registry for this resource.
*
*
This is in the (\w|_){1,80}-\w{1,8} format specified by RFC 5730 for roidType.
+ *
* @see RFC 5730
*/
- @Id @javax.persistence.Id String repoId;
+ @Id
+ // not persisted so that we can store these in references to other objects. Subclasses that wish
+ // to use this as the primary key should create a getter method annotated with @Id
+ @Transient
+ String repoId;
/** The ID of the registrar that is currently sponsoring this resource. */
@Index
@@ -138,6 +146,12 @@ public abstract class EppResource extends BackupGroupRoot implements Buildable {
return repoId;
}
+ // Hibernate needs this to populate the repo ID, but no one else should ever use it
+ @SuppressWarnings("UnusedMethod")
+ private void setRepoId(String repoId) {
+ this.repoId = repoId;
+ }
+
public final DateTime getCreationTime() {
return creationTime.getTimestamp();
}
@@ -214,7 +228,7 @@ public abstract class EppResource extends BackupGroupRoot implements Buildable {
}
/** Abstract builder for {@link EppResource} types. */
- public abstract static class Builder>
+ public abstract static class Builder>
extends GenericBuilder {
/** Create a {@link Builder} wrapping a new instance. */
diff --git a/core/src/main/java/google/registry/model/contact/ContactResource.java b/core/src/main/java/google/registry/model/contact/ContactResource.java
index 30a984fdf..aabbb883a 100644
--- a/core/src/main/java/google/registry/model/contact/ContactResource.java
+++ b/core/src/main/java/google/registry/model/contact/ContactResource.java
@@ -37,6 +37,8 @@ import google.registry.schema.replay.DatastoreAndSqlEntity;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Stream;
+import javax.persistence.Access;
+import javax.persistence.AccessType;
import javax.persistence.AttributeOverride;
import javax.persistence.AttributeOverrides;
import javax.persistence.Column;
@@ -63,6 +65,7 @@ import org.joda.time.DateTime;
})
@ExternalMessagingName("contact")
@WithStringVKey
+@Access(AccessType.FIELD)
public class ContactResource extends EppResource
implements DatastoreAndSqlEntity, ForeignKeyedEppResource, ResourceWithTransferData {
@@ -201,6 +204,13 @@ public class ContactResource extends EppResource
return VKey.createOfy(ContactResource.class, Key.create(this));
}
+ @Override
+ @javax.persistence.Id
+ @Access(AccessType.PROPERTY)
+ public String getRepoId() {
+ return super.getRepoId();
+ }
+
public String getContactId() {
return contactId;
}
diff --git a/core/src/main/java/google/registry/model/domain/DomainBase.java b/core/src/main/java/google/registry/model/domain/DomainBase.java
index 7aa489ce1..cb40f4ee2 100644
--- a/core/src/main/java/google/registry/model/domain/DomainBase.java
+++ b/core/src/main/java/google/registry/model/domain/DomainBase.java
@@ -75,6 +75,8 @@ import java.util.Optional;
import java.util.Set;
import java.util.function.Predicate;
import javax.annotation.Nullable;
+import javax.persistence.Access;
+import javax.persistence.AccessType;
import javax.persistence.AttributeOverride;
import javax.persistence.AttributeOverrides;
import javax.persistence.Column;
@@ -109,6 +111,7 @@ import org.joda.time.Interval;
})
@WithStringVKey
@ExternalMessagingName("domain")
+@Access(AccessType.FIELD)
public class DomainBase extends EppResource
implements DatastoreAndSqlEntity,
ForeignKeyedEppResource,
@@ -295,6 +298,13 @@ public class DomainBase extends EppResource
allContacts = contactsBuilder.build();
}
+ @Override
+ @javax.persistence.Id
+ @Access(AccessType.PROPERTY)
+ public String getRepoId() {
+ return super.getRepoId();
+ }
+
public ImmutableSet getSubordinateHosts() {
return nullToEmptyImmutableCopy(subordinateHosts);
}
diff --git a/core/src/main/java/google/registry/model/eppcommon/StatusValue.java b/core/src/main/java/google/registry/model/eppcommon/StatusValue.java
index 9433e9482..644075e7a 100644
--- a/core/src/main/java/google/registry/model/eppcommon/StatusValue.java
+++ b/core/src/main/java/google/registry/model/eppcommon/StatusValue.java
@@ -22,6 +22,7 @@ import com.google.common.collect.ImmutableSet;
import google.registry.model.EppResource;
import google.registry.model.contact.ContactResource;
import google.registry.model.domain.DomainBase;
+import google.registry.model.host.HostBase;
import google.registry.model.host.HostResource;
import google.registry.model.translators.EnumToAttributeAdapter.EppEnum;
import google.registry.model.translators.StatusValueAdapter;
@@ -127,7 +128,7 @@ public enum StatusValue implements EppEnum {
/** Enum to help clearly list which resource types a status value is allowed to be present on. */
private enum AllowedOn {
- ALL(ContactResource.class, DomainBase.class, HostResource.class),
+ ALL(ContactResource.class, DomainBase.class, HostBase.class, HostResource.class),
NONE,
DOMAINS(DomainBase.class);
diff --git a/core/src/main/java/google/registry/model/host/HostBase.java b/core/src/main/java/google/registry/model/host/HostBase.java
new file mode 100644
index 000000000..ab4f4d2cb
--- /dev/null
+++ b/core/src/main/java/google/registry/model/host/HostBase.java
@@ -0,0 +1,229 @@
+// 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.
+
+package google.registry.model.host;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.collect.Sets.difference;
+import static com.google.common.collect.Sets.union;
+import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy;
+import static google.registry.util.DateTimeUtils.START_OF_TIME;
+import static google.registry.util.DomainNameUtils.canonicalizeDomainName;
+
+import com.google.common.collect.ImmutableSet;
+import com.googlecode.objectify.Key;
+import com.googlecode.objectify.annotation.IgnoreSave;
+import com.googlecode.objectify.annotation.Index;
+import com.googlecode.objectify.condition.IfNull;
+import google.registry.model.EppResource;
+import google.registry.model.domain.DomainBase;
+import google.registry.model.transfer.TransferData;
+import google.registry.persistence.VKey;
+import java.net.InetAddress;
+import java.util.Optional;
+import java.util.Set;
+import javax.annotation.Nullable;
+import javax.persistence.Access;
+import javax.persistence.AccessType;
+import javax.persistence.Embeddable;
+import javax.persistence.MappedSuperclass;
+import org.joda.time.DateTime;
+
+/**
+ * A persistable Host resource including mutable and non-mutable fields.
+ *
+ * A host's {@link TransferData} is stored on the superordinate domain. Non-subordinate hosts
+ * don't carry a full set of TransferData; all they have is lastTransferTime.
+ *
+ *
This class deliberately does not include an {@link javax.persistence.Id} so that any
+ * foreign-keyed fields can refer to the proper parent entity's ID, whether we're storing this in
+ * the DB itself or as part of another entity
+ *
+ * @see RFC 5732
+ */
+@MappedSuperclass
+@Embeddable
+@Access(AccessType.FIELD)
+public class HostBase extends EppResource {
+
+ /**
+ * Fully qualified hostname, which is a unique identifier for this host.
+ *
+ *
This is only unique in the sense that for any given lifetime specified as the time range
+ * from (creationTime, deletionTime) there can only be one host in Datastore with this name.
+ * However, there can be many hosts with the same name and non-overlapping lifetimes.
+ */
+ @Index String fullyQualifiedHostName;
+
+ /** IP Addresses for this host. Can be null if this is an external host. */
+ @Index Set inetAddresses;
+
+ /** The superordinate domain of this host, or null if this is an external host. */
+ @Index
+ @IgnoreSave(IfNull.class)
+ @DoNotHydrate
+ VKey superordinateDomain;
+
+ /**
+ * The time that this resource was last transferred.
+ *
+ * Can be null if the resource has never been transferred.
+ */
+ DateTime lastTransferTime;
+
+ /**
+ * The most recent time that the {@link #superordinateDomain} field was changed.
+ *
+ *
This should be updated whenever the superordinate domain changes, including when it is set
+ * to null. This field will be null for new hosts that have never experienced a change of
+ * superordinate domain.
+ */
+ DateTime lastSuperordinateChange;
+
+ public String getFullyQualifiedHostName() {
+ return fullyQualifiedHostName;
+ }
+
+ public VKey getSuperordinateDomain() {
+ return superordinateDomain;
+ }
+
+ public boolean isSubordinate() {
+ return superordinateDomain != null;
+ }
+
+ public ImmutableSet getInetAddresses() {
+ return nullToEmptyImmutableCopy(inetAddresses);
+ }
+
+ public DateTime getLastTransferTime() {
+ return lastTransferTime;
+ }
+
+ public DateTime getLastSuperordinateChange() {
+ return lastSuperordinateChange;
+ }
+
+ @Override
+ public String getForeignKey() {
+ return fullyQualifiedHostName;
+ }
+
+ @Override
+ public VKey extends EppResource> createVKey() {
+ return VKey.createOfy(HostBase.class, Key.create(this));
+ }
+
+ @Deprecated
+ @Override
+ public HostBase cloneProjectedAtTime(DateTime now) {
+ return this;
+ }
+
+ @Override
+ public Builder asBuilder() {
+ return new Builder<>(clone(this));
+ }
+
+ /**
+ * Compute the correct last transfer time for this host given its loaded superordinate domain.
+ *
+ * Hosts can move between superordinate domains, so to know which lastTransferTime is correct
+ * we need to know if the host was attached to this superordinate the last time that the
+ * superordinate was transferred. If the last superordinate change was before this time, then the
+ * host was attached to this superordinate domain during that transfer.
+ *
+ *
If the host is not subordinate the domain can be null and we just return last transfer time.
+ *
+ * @param superordinateDomain the loaded superordinate domain, which must match the key in the
+ * {@link #superordinateDomain} field. Passing it as a parameter allows the caller to control
+ * the degree of consistency used to load it.
+ */
+ public DateTime computeLastTransferTime(@Nullable DomainBase superordinateDomain) {
+ if (!isSubordinate()) {
+ checkArgument(superordinateDomain == null);
+ return getLastTransferTime();
+ }
+ checkArgument(
+ superordinateDomain != null
+ && superordinateDomain.createVKey().equals(getSuperordinateDomain()));
+ DateTime lastSuperordinateChange =
+ Optional.ofNullable(getLastSuperordinateChange()).orElse(getCreationTime());
+ DateTime lastTransferOfCurrentSuperordinate =
+ Optional.ofNullable(superordinateDomain.getLastTransferTime()).orElse(START_OF_TIME);
+ return lastSuperordinateChange.isBefore(lastTransferOfCurrentSuperordinate)
+ ? superordinateDomain.getLastTransferTime()
+ : getLastTransferTime();
+ }
+
+ /** A builder for constructing {@link HostBase}, since it is immutable. */
+ protected static class Builder>
+ extends EppResource.Builder {
+ public Builder() {}
+
+ protected Builder(T instance) {
+ super(instance);
+ }
+
+ // Strangely, if we don't add these @Overrides the methods return an EppResource.Builder
+ // even though we parameterize it with B in both cases anyway.
+ @Override
+ public B setRepoId(String repoId) {
+ return super.setRepoId(repoId);
+ }
+
+ @Override
+ public T build() {
+ return super.build();
+ }
+
+ public B setFullyQualifiedHostName(String fullyQualifiedHostName) {
+ checkArgument(
+ fullyQualifiedHostName.equals(canonicalizeDomainName(fullyQualifiedHostName)),
+ "Host name must be in puny-coded, lower-case form");
+ getInstance().fullyQualifiedHostName = fullyQualifiedHostName;
+ return thisCastToDerived();
+ }
+
+ public B setInetAddresses(ImmutableSet inetAddresses) {
+ getInstance().inetAddresses = inetAddresses;
+ return thisCastToDerived();
+ }
+
+ public B setLastSuperordinateChange(DateTime lastSuperordinateChange) {
+ getInstance().lastSuperordinateChange = lastSuperordinateChange;
+ return thisCastToDerived();
+ }
+
+ public B addInetAddresses(ImmutableSet inetAddresses) {
+ return setInetAddresses(
+ ImmutableSet.copyOf(union(getInstance().getInetAddresses(), inetAddresses)));
+ }
+
+ public B removeInetAddresses(ImmutableSet inetAddresses) {
+ return setInetAddresses(
+ ImmutableSet.copyOf(difference(getInstance().getInetAddresses(), inetAddresses)));
+ }
+
+ public B setSuperordinateDomain(VKey superordinateDomain) {
+ getInstance().superordinateDomain = superordinateDomain;
+ return thisCastToDerived();
+ }
+
+ public B setLastTransferTime(DateTime lastTransferTime) {
+ getInstance().lastTransferTime = lastTransferTime;
+ return thisCastToDerived();
+ }
+ }
+}
diff --git a/core/src/main/java/google/registry/model/host/HostHistory.java b/core/src/main/java/google/registry/model/host/HostHistory.java
new file mode 100644
index 000000000..79bfead6d
--- /dev/null
+++ b/core/src/main/java/google/registry/model/host/HostHistory.java
@@ -0,0 +1,90 @@
+// 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.
+
+package google.registry.model.host;
+
+import com.googlecode.objectify.Key;
+import google.registry.model.EppResource;
+import google.registry.model.reporting.HistoryEntry;
+import google.registry.persistence.VKey;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+
+/**
+ * A persisted history entry representing an EPP modification to a host.
+ *
+ * In addition to the general history fields (e.g. action time, registrar ID) we also persist a
+ * copy of the host entity at this point in time. We persist a raw {@link HostBase} so that the
+ * foreign-keyed fields in that class can refer to this object.
+ */
+@Entity
+@javax.persistence.Table(
+ indexes = {
+ @javax.persistence.Index(columnList = "creationTime"),
+ @javax.persistence.Index(columnList = "historyRegistrarId"),
+ @javax.persistence.Index(columnList = "fullyQualifiedHostName"),
+ @javax.persistence.Index(columnList = "historyType"),
+ @javax.persistence.Index(columnList = "historyModificationTime")
+ })
+public class HostHistory extends HistoryEntry {
+
+ // Store HostBase instead of HostResource so we don't pick up its @Id
+ HostBase hostBase;
+
+ @Column(nullable = false)
+ VKey hostRepoId;
+
+ /** The state of the {@link HostBase} object at this point in time. */
+ public HostBase getHostBase() {
+ return hostBase;
+ }
+
+ /** The key to the {@link google.registry.model.host.HostResource} this is based off of. */
+ public VKey getHostRepoId() {
+ return hostRepoId;
+ }
+
+ @Override
+ public Builder asBuilder() {
+ return new Builder(clone(this));
+ }
+
+ public static class Builder extends HistoryEntry.Builder {
+
+ public Builder() {}
+
+ public Builder(HostHistory instance) {
+ super(instance);
+ }
+
+ public Builder setHostBase(HostBase hostBase) {
+ getInstance().hostBase = hostBase;
+ return this;
+ }
+
+ public Builder setHostResourceId(VKey hostRepoId) {
+ getInstance().hostRepoId = hostRepoId;
+ hostRepoId.maybeGetOfyKey().ifPresent(parent -> getInstance().parent = parent);
+ return this;
+ }
+
+ // We can remove this once all HistoryEntries are converted to History objects
+ @Override
+ public Builder setParent(Key extends EppResource> parent) {
+ super.setParent(parent);
+ getInstance().hostRepoId = VKey.createOfy(HostResource.class, (Key) parent);
+ return this;
+ }
+ }
+}
diff --git a/core/src/main/java/google/registry/model/host/HostResource.java b/core/src/main/java/google/registry/model/host/HostResource.java
index 0c8c8fd15..57e6bfbda 100644
--- a/core/src/main/java/google/registry/model/host/HostResource.java
+++ b/core/src/main/java/google/registry/model/host/HostResource.java
@@ -14,112 +14,36 @@
package google.registry.model.host;
-import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.collect.Sets.difference;
-import static com.google.common.collect.Sets.union;
-import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy;
-import static google.registry.util.DateTimeUtils.START_OF_TIME;
-import static google.registry.util.DomainNameUtils.canonicalizeDomainName;
-
-import com.google.common.collect.ImmutableSet;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.annotation.Entity;
-import com.googlecode.objectify.annotation.IgnoreSave;
-import com.googlecode.objectify.annotation.Index;
-import com.googlecode.objectify.condition.IfNull;
-import google.registry.model.EppResource;
import google.registry.model.EppResource.ForeignKeyedEppResource;
import google.registry.model.annotations.ExternalMessagingName;
import google.registry.model.annotations.ReportedOn;
-import google.registry.model.domain.DomainBase;
-import google.registry.model.transfer.TransferData;
import google.registry.persistence.VKey;
import google.registry.persistence.WithStringVKey;
import google.registry.schema.replay.DatastoreAndSqlEntity;
-import java.net.InetAddress;
-import java.util.Optional;
-import java.util.Set;
-import javax.annotation.Nullable;
-import org.joda.time.DateTime;
+import javax.persistence.Access;
+import javax.persistence.AccessType;
/**
* A persistable Host resource including mutable and non-mutable fields.
*
- * A host's {@link TransferData} is stored on the superordinate domain. Non-subordinate hosts
- * don't carry a full set of TransferData; all they have is lastTransferTime.
- *
- * @see RFC 5732
+ *
The {@link javax.persistence.Id} of the HostResource is the repoId.
*/
@ReportedOn
@Entity
@javax.persistence.Entity
@ExternalMessagingName("host")
@WithStringVKey
-public class HostResource extends EppResource
+@Access(AccessType.FIELD) // otherwise it'll use the default if the repoId (property)
+public class HostResource extends HostBase
implements DatastoreAndSqlEntity, ForeignKeyedEppResource {
- /**
- * Fully qualified hostname, which is a unique identifier for this host.
- *
- *
This is only unique in the sense that for any given lifetime specified as the time range
- * from (creationTime, deletionTime) there can only be one host in Datastore with this name.
- * However, there can be many hosts with the same name and non-overlapping lifetimes.
- */
- @Index
- String fullyQualifiedHostName;
-
- /** IP Addresses for this host. Can be null if this is an external host. */
- @Index Set inetAddresses;
-
- /** The superordinate domain of this host, or null if this is an external host. */
- @Index
- @IgnoreSave(IfNull.class)
- @DoNotHydrate
- VKey superordinateDomain;
-
- /**
- * The time that this resource was last transferred.
- *
- * Can be null if the resource has never been transferred.
- */
- DateTime lastTransferTime;
-
- /**
- * The most recent time that the {@link #superordinateDomain} field was changed.
- *
- *
This should be updated whenever the superordinate domain changes, including when it is set
- * to null. This field will be null for new hosts that have never experienced a change of
- * superordinate domain.
- */
- DateTime lastSuperordinateChange;
-
- public String getFullyQualifiedHostName() {
- return fullyQualifiedHostName;
- }
-
- public VKey getSuperordinateDomain() {
- return superordinateDomain;
- }
-
- public boolean isSubordinate() {
- return superordinateDomain != null;
- }
-
- public ImmutableSet getInetAddresses() {
- return nullToEmptyImmutableCopy(inetAddresses);
- }
-
- public DateTime getLastTransferTime() {
- return lastTransferTime;
- }
-
- public DateTime getLastSuperordinateChange() {
- return lastSuperordinateChange;
- }
-
@Override
- public String getForeignKey() {
- return fullyQualifiedHostName;
+ @javax.persistence.Id
+ @Access(AccessType.PROPERTY) // to tell it to use the non-default property-as-ID
+ public String getRepoId() {
+ return super.getRepoId();
}
@Override
@@ -128,92 +52,17 @@ public class HostResource extends EppResource
return VKey.createOfy(HostResource.class, Key.create(this));
}
- @Deprecated
- @Override
- public HostResource cloneProjectedAtTime(DateTime now) {
- return this;
- }
-
- /**
- * Compute the correct last transfer time for this host given its loaded superordinate domain.
- *
- * Hosts can move between superordinate domains, so to know which lastTransferTime is correct
- * we need to know if the host was attached to this superordinate the last time that the
- * superordinate was transferred. If the last superordinate change was before this time, then the
- * host was attached to this superordinate domain during that transfer.
- *
- *
If the host is not subordinate the domain can be null and we just return last transfer time.
- *
- * @param superordinateDomain the loaded superordinate domain, which must match the key in the
- * {@link #superordinateDomain} field. Passing it as a parameter allows the caller to control
- * the degree of consistency used to load it.
- */
- public DateTime computeLastTransferTime(@Nullable DomainBase superordinateDomain) {
- if (!isSubordinate()) {
- checkArgument(superordinateDomain == null);
- return getLastTransferTime();
- }
- checkArgument(
- superordinateDomain != null
- && superordinateDomain.createVKey().equals(getSuperordinateDomain()));
- DateTime lastSuperordinateChange =
- Optional.ofNullable(getLastSuperordinateChange()).orElse(getCreationTime());
- DateTime lastTransferOfCurrentSuperordinate =
- Optional.ofNullable(superordinateDomain.getLastTransferTime()).orElse(START_OF_TIME);
- return lastSuperordinateChange.isBefore(lastTransferOfCurrentSuperordinate)
- ? superordinateDomain.getLastTransferTime()
- : getLastTransferTime();
- }
-
@Override
public Builder asBuilder() {
return new Builder(clone(this));
}
/** A builder for constructing {@link HostResource}, since it is immutable. */
- public static class Builder extends EppResource.Builder {
+ public static class Builder extends HostBase.Builder {
public Builder() {}
private Builder(HostResource instance) {
super(instance);
}
-
- public Builder setFullyQualifiedHostName(String fullyQualifiedHostName) {
- checkArgument(
- fullyQualifiedHostName.equals(canonicalizeDomainName(fullyQualifiedHostName)),
- "Host name must be in puny-coded, lower-case form");
- getInstance().fullyQualifiedHostName = fullyQualifiedHostName;
- return this;
- }
-
- public Builder setInetAddresses(ImmutableSet inetAddresses) {
- getInstance().inetAddresses = inetAddresses;
- return this;
- }
-
- public Builder setLastSuperordinateChange(DateTime lastSuperordinateChange) {
- getInstance().lastSuperordinateChange = lastSuperordinateChange;
- return this;
- }
-
- public Builder addInetAddresses(ImmutableSet inetAddresses) {
- return setInetAddresses(ImmutableSet.copyOf(
- union(getInstance().getInetAddresses(), inetAddresses)));
- }
-
- public Builder removeInetAddresses(ImmutableSet inetAddresses) {
- return setInetAddresses(ImmutableSet.copyOf(
- difference(getInstance().getInetAddresses(), inetAddresses)));
- }
-
- public Builder setSuperordinateDomain(VKey superordinateDomain) {
- getInstance().superordinateDomain = superordinateDomain;
- return this;
- }
-
- public Builder setLastTransferTime(DateTime lastTransferTime) {
- getInstance().lastTransferTime = lastTransferTime;
- return this;
- }
}
}
diff --git a/core/src/main/java/google/registry/model/reporting/HistoryEntry.java b/core/src/main/java/google/registry/model/reporting/HistoryEntry.java
index c2a0860e1..af9bd54ca 100644
--- a/core/src/main/java/google/registry/model/reporting/HistoryEntry.java
+++ b/core/src/main/java/google/registry/model/reporting/HistoryEntry.java
@@ -32,11 +32,22 @@ import google.registry.model.domain.Period;
import google.registry.model.eppcommon.Trid;
import java.util.Set;
import javax.annotation.Nullable;
+import javax.persistence.AttributeOverride;
+import javax.persistence.AttributeOverrides;
+import javax.persistence.Column;
+import javax.persistence.EnumType;
+import javax.persistence.Enumerated;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.MappedSuperclass;
+import javax.persistence.SequenceGenerator;
+import javax.persistence.Transient;
import org.joda.time.DateTime;
/** A record of an EPP command that mutated a resource. */
@ReportedOn
@Entity
+@MappedSuperclass
public class HistoryEntry extends ImmutableObject implements Buildable {
/** Represents the type of history entry. */
@@ -60,8 +71,8 @@ public class HistoryEntry extends ImmutableObject implements Buildable {
@Deprecated
DOMAIN_ALLOCATE,
/**
- * Used for domain registration autorenews explicitly logged by
- * {@link google.registry.batch.ExpandRecurringBillingEventsAction}.
+ * Used for domain registration autorenews explicitly logged by {@link
+ * google.registry.batch.ExpandRecurringBillingEventsAction}.
*/
DOMAIN_AUTORENEW,
DOMAIN_CREATE,
@@ -89,14 +100,22 @@ public class HistoryEntry extends ImmutableObject implements Buildable {
}
/** The autogenerated id of this event. */
+ @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "HistorySequenceGenerator")
+ @SequenceGenerator(
+ name = "HistorySequenceGenerator",
+ sequenceName = "history_id_sequence",
+ allocationSize = 1)
@Id
- long id;
+ @javax.persistence.Id
+ @Column(name = "historyRevisionId")
+ Long id;
/** The resource this event mutated. */
- @Parent
- Key extends EppResource> parent;
+ @Parent @Transient protected Key extends EppResource> parent;
/** The type of history entry. */
+ @Column(nullable = false, name = "historyType")
+ @Enumerated(EnumType.STRING)
Type type;
/**
@@ -104,17 +123,21 @@ public class HistoryEntry extends ImmutableObject implements Buildable {
* be null for all other types.
*/
@IgnoreSave(IfNull.class)
+ @Transient // domain-specific
Period period;
/** The actual EPP xml of the command, stored as bytes to be agnostic of encoding. */
+ @Column(nullable = false, name = "historyXmlBytes")
byte[] xmlBytes;
- /** The time the command occurred, represented by the ofy transaction time.*/
+ /** The time the command occurred, represented by the ofy transaction time. */
@Index
+ @Column(nullable = false, name = "historyModificationTime")
DateTime modificationTime;
/** The id of the registrar that sent the command. */
@Index
+ @Column(name = "historyRegistrarId")
String clientId;
/**
@@ -124,18 +147,31 @@ public class HistoryEntry extends ImmutableObject implements Buildable {
* sending the EPP transfer command is the gaining party). For approves and rejects, the other
* registrar is the gaining party.
*/
+ @Transient // domain-specific
String otherClientId;
/** Transaction id that made this change, or null if the entry was not created by a flow. */
- @Nullable Trid trid;
+ @Nullable
+ @AttributeOverrides({
+ @AttributeOverride(
+ name = "clientTransactionId",
+ column = @Column(name = "historyClientTransactionId")),
+ @AttributeOverride(
+ name = "serverTransactionId",
+ column = @Column(name = "historyServerTransactionId"))
+ })
+ Trid trid;
/** Whether this change was created by a superuser. */
+ @Column(nullable = false, name = "historyBySuperuser")
boolean bySuperuser;
/** Reason for the change. */
+ @Column(nullable = false, name = "historyReason")
String reason;
/** Whether this change was requested by a registrar. */
+ @Column(nullable = false, name = "historyRequestedByRegistrar")
Boolean requestedByRegistrar;
/**
@@ -145,6 +181,7 @@ public class HistoryEntry extends ImmutableObject implements Buildable {
* also be empty if the HistoryEntry refers to an EPP mutation that does not affect domain
* transaction counts (such as contact or host mutations).
*/
+ @Transient // domain-specific
Set domainTransactionRecords;
public Key extends EppResource> getParent() {
@@ -176,7 +213,8 @@ public class HistoryEntry extends ImmutableObject implements Buildable {
}
/** Returns the TRID, which may be null if the entry was not created by a normal flow. */
- @Nullable public Trid getTrid() {
+ @Nullable
+ public Trid getTrid() {
return trid;
}
@@ -202,77 +240,84 @@ public class HistoryEntry extends ImmutableObject implements Buildable {
}
/** A builder for {@link HistoryEntry} since it is immutable */
- public static class Builder extends Buildable.Builder {
+ public static class Builder>
+ extends GenericBuilder {
public Builder() {}
- public Builder(HistoryEntry instance) {
+ public Builder(T instance) {
super(instance);
}
- public Builder setParent(EppResource parent) {
+ @Override
+ public T build() {
+ return super.build();
+ }
+
+ public B setParent(EppResource parent) {
getInstance().parent = Key.create(parent);
- return this;
+ return thisCastToDerived();
}
- public Builder setParent(Key extends EppResource> parentKey) {
- getInstance().parent = parentKey;
- return this;
+ // Until we move completely to SQL, override this in subclasses (e.g. HostHistory) to set VKeys
+ public B setParent(Key extends EppResource> parent) {
+ getInstance().parent = parent;
+ return thisCastToDerived();
}
- public Builder setType(Type type) {
+ public B setType(Type type) {
getInstance().type = type;
- return this;
+ return thisCastToDerived();
}
- public Builder setPeriod(Period period) {
+ public B setPeriod(Period period) {
getInstance().period = period;
- return this;
+ return thisCastToDerived();
}
- public Builder setXmlBytes(byte[] xmlBytes) {
+ public B setXmlBytes(byte[] xmlBytes) {
getInstance().xmlBytes = xmlBytes;
- return this;
+ return thisCastToDerived();
}
- public Builder setModificationTime(DateTime modificationTime) {
+ public B setModificationTime(DateTime modificationTime) {
getInstance().modificationTime = modificationTime;
- return this;
+ return thisCastToDerived();
}
- public Builder setClientId(String clientId) {
+ public B setClientId(String clientId) {
getInstance().clientId = clientId;
- return this;
+ return thisCastToDerived();
}
- public Builder setOtherClientId(String otherClientId) {
+ public B setOtherClientId(String otherClientId) {
getInstance().otherClientId = otherClientId;
- return this;
+ return thisCastToDerived();
}
- public Builder setTrid(Trid trid) {
+ public B setTrid(Trid trid) {
getInstance().trid = trid;
- return this;
+ return thisCastToDerived();
}
- public Builder setBySuperuser(boolean bySuperuser) {
+ public B setBySuperuser(boolean bySuperuser) {
getInstance().bySuperuser = bySuperuser;
- return this;
+ return thisCastToDerived();
}
- public Builder setReason(String reason) {
+ public B setReason(String reason) {
getInstance().reason = reason;
- return this;
+ return thisCastToDerived();
}
- public Builder setRequestedByRegistrar(Boolean requestedByRegistrar) {
+ public B setRequestedByRegistrar(Boolean requestedByRegistrar) {
getInstance().requestedByRegistrar = requestedByRegistrar;
- return this;
+ return thisCastToDerived();
}
- public Builder setDomainTransactionRecords(
+ public B setDomainTransactionRecords(
ImmutableSet domainTransactionRecords) {
getInstance().domainTransactionRecords = domainTransactionRecords;
- return this;
+ return thisCastToDerived();
}
}
}
diff --git a/core/src/main/resources/META-INF/persistence.xml b/core/src/main/resources/META-INF/persistence.xml
index f5125be74..61e7937b0 100644
--- a/core/src/main/resources/META-INF/persistence.xml
+++ b/core/src/main/resources/META-INF/persistence.xml
@@ -24,6 +24,7 @@
google.registry.model.billing.BillingEvent$Recurring
google.registry.model.contact.ContactResource
google.registry.model.domain.DomainBase
+ google.registry.model.host.HostHistory
google.registry.model.host.HostResource
google.registry.model.registrar.Registrar
google.registry.model.registrar.RegistrarContact
@@ -63,8 +64,8 @@
google.registry.model.billing.VKeyConverter_BillingEvent
- google.registry.model.domain.VKeyConverter_DomainBase
google.registry.model.contact.VKeyConverter_ContactResource
+ google.registry.model.domain.VKeyConverter_DomainBase
google.registry.model.domain.token.VKeyConverter_AllocationToken
google.registry.model.host.VKeyConverter_HostResource
google.registry.model.poll.VKeyConverter_Autorenew
diff --git a/core/src/test/java/google/registry/model/history/HostHistoryTest.java b/core/src/test/java/google/registry/model/history/HostHistoryTest.java
new file mode 100644
index 000000000..4c341dc49
--- /dev/null
+++ b/core/src/test/java/google/registry/model/history/HostHistoryTest.java
@@ -0,0 +1,87 @@
+// Copyright 2017 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.
+
+package google.registry.model.history;
+
+import static com.google.common.truth.Truth.assertThat;
+import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
+import static google.registry.testing.SqlHelper.saveRegistrar;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.google.common.collect.ImmutableSet;
+import google.registry.model.EntityTestCase;
+import google.registry.model.eppcommon.Trid;
+import google.registry.model.host.HostHistory;
+import google.registry.model.host.HostResource;
+import google.registry.model.reporting.HistoryEntry;
+import google.registry.persistence.VKey;
+import org.junit.jupiter.api.Test;
+
+/** Tests for {@link HostHistory}. */
+public class HostHistoryTest extends EntityTestCase {
+
+ public HostHistoryTest() {
+ super(true);
+ }
+
+ @Test
+ public void testPersistence() {
+ saveRegistrar("registrar1");
+
+ HostResource host =
+ new HostResource.Builder()
+ .setRepoId("host1")
+ .setFullyQualifiedHostName("ns1.example.com")
+ .setCreationClientId("TheRegistrar")
+ .setPersistedCurrentSponsorClientId("TheRegistrar")
+ .setInetAddresses(ImmutableSet.of())
+ .build();
+ jpaTm().transact(() -> jpaTm().saveNew(host));
+ VKey hostVKey = VKey.createSql(HostResource.class, "host1");
+ HostResource hostFromDb = jpaTm().transact(() -> jpaTm().load(hostVKey));
+ HostHistory hostHistory =
+ new HostHistory.Builder()
+ .setType(HistoryEntry.Type.HOST_CREATE)
+ .setXmlBytes("".getBytes(UTF_8))
+ .setModificationTime(fakeClock.nowUtc())
+ .setClientId("registrar1")
+ .setTrid(Trid.create("ABC-123", "server-trid"))
+ .setBySuperuser(false)
+ .setReason("reason")
+ .setRequestedByRegistrar(true)
+ .setHostBase(hostFromDb)
+ .setHostResourceId(hostVKey)
+ .build();
+ jpaTm().transact(() -> jpaTm().saveNew(hostHistory));
+ jpaTm()
+ .transact(
+ () -> {
+ HostHistory fromDatabase = jpaTm().load(VKey.createSql(HostHistory.class, 1L));
+ assertHostHistoriesEqual(fromDatabase, hostHistory);
+ });
+ }
+
+ private void assertHostHistoriesEqual(HostHistory one, HostHistory two) {
+ // enough of the fields get changed during serialization that we can't depend on .equals()
+ assertThat(one.getClientId()).isEqualTo(two.getClientId());
+ assertThat(one.getHostRepoId()).isEqualTo(two.getHostRepoId());
+ assertThat(one.getBySuperuser()).isEqualTo(two.getBySuperuser());
+ assertThat(one.getRequestedByRegistrar()).isEqualTo(two.getRequestedByRegistrar());
+ assertThat(one.getReason()).isEqualTo(two.getReason());
+ assertThat(one.getTrid()).isEqualTo(two.getTrid());
+ assertThat(one.getType()).isEqualTo(two.getType());
+ assertThat(one.getHostBase().getFullyQualifiedHostName())
+ .isEqualTo(two.getHostBase().getFullyQualifiedHostName());
+ }
+}
diff --git a/core/src/test/java/google/registry/schema/integration/SqlIntegrationTestSuite.java b/core/src/test/java/google/registry/schema/integration/SqlIntegrationTestSuite.java
index 13ed2849c..343367115 100644
--- a/core/src/test/java/google/registry/schema/integration/SqlIntegrationTestSuite.java
+++ b/core/src/test/java/google/registry/schema/integration/SqlIntegrationTestSuite.java
@@ -19,6 +19,7 @@ import static com.google.common.truth.Truth.assert_;
import google.registry.model.billing.BillingEventTest;
import google.registry.model.contact.ContactResourceTest;
import google.registry.model.domain.DomainBaseSqlTest;
+import google.registry.model.history.HostHistoryTest;
import google.registry.model.poll.PollMessageTest;
import google.registry.model.registry.RegistryLockDaoTest;
import google.registry.persistence.transaction.JpaEntityCoverage;
@@ -75,6 +76,7 @@ import org.junit.runner.RunWith;
ContactResourceTest.class,
CursorDaoTest.class,
DomainBaseSqlTest.class,
+ HostHistoryTest.class,
LockDaoTest.class,
PollMessageTest.class,
PremiumListDaoTest.class,
diff --git a/core/src/test/resources/google/registry/model/schema.txt b/core/src/test/resources/google/registry/model/schema.txt
index 509d7f892..bdea2a8f0 100644
--- a/core/src/test/resources/google/registry/model/schema.txt
+++ b/core/src/test/resources/google/registry/model/schema.txt
@@ -618,7 +618,7 @@ enum google.registry.model.reporting.DomainTransactionRecord$TransactionReportFi
TRANSFER_SUCCESSFUL;
}
class google.registry.model.reporting.HistoryEntry {
- @Id long id;
+ @Id java.lang.Long id;
@Parent com.googlecode.objectify.Key extends google.registry.model.EppResource> parent;
boolean bySuperuser;
byte[] xmlBytes;
diff --git a/db/src/main/resources/sql/flyway/V32__create_host_history.sql b/db/src/main/resources/sql/flyway/V32__create_host_history.sql
new file mode 100644
index 000000000..7cb7246cc
--- /dev/null
+++ b/db/src/main/resources/sql/flyway/V32__create_host_history.sql
@@ -0,0 +1,66 @@
+-- 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.
+
+CREATE TABLE "HostHistory" (
+ history_revision_id int8 NOT NULL,
+ history_by_superuser boolean NOT NULL,
+ history_registrar_id text NOT NULL,
+ history_modification_time timestamptz NOT NULL,
+ history_reason text NOT NULL,
+ history_requested_by_registrar boolean NOT NULL,
+ history_client_transaction_id text,
+ history_server_transaction_id text,
+ history_type text NOT NULL,
+ history_xml_bytes bytea NOT NULL,
+ fully_qualified_host_name text,
+ inet_addresses text[],
+ last_superordinate_change timestamptz,
+ last_transfer_time timestamptz,
+ superordinate_domain text,
+ creation_registrar_id text NOT NULL,
+ creation_time timestamptz NOT NULL,
+ current_sponsor_registrar_id text NOT NULL,
+ deletion_time timestamptz,
+ last_epp_update_registrar_id text,
+ last_epp_update_time timestamptz,
+ statuses text[],
+ host_repo_id text NOT NULL,
+ primary key (history_revision_id)
+);
+
+CREATE INDEX IDXfg2nnjlujxo6cb9fha971bq2n ON "HostHistory" (creation_time);
+CREATE INDEX IDX1iy7njgb7wjmj9piml4l2g0qi ON "HostHistory" (history_registrar_id);
+CREATE INDEX IDXj77pfwhui9f0i7wjq6lmibovj ON "HostHistory" (fully_qualified_host_name);
+CREATE INDEX IDXknk8gmj7s47q56cwpa6rmpt5l ON "HostHistory" (history_type);
+CREATE INDEX IDX67qwkjtlq5q8dv6egtrtnhqi7 ON "HostHistory" (history_modification_time);
+
+ALTER TABLE IF EXISTS "HostHistory"
+ ADD CONSTRAINT FK3d09knnmxrt6iniwnp8j2ykga
+ FOREIGN KEY (history_registrar_id)
+ REFERENCES "Registrar";
+
+ALTER TABLE IF EXISTS "HostHistory"
+ ADD CONSTRAINT FK_HostHistory_HostResource
+ FOREIGN KEY (host_repo_id)
+ REFERENCES "HostResource";
+
+CREATE SEQUENCE public."history_id_sequence"
+ START WITH 1
+ INCREMENT BY 1
+ NO MINVALUE
+ NO MAXVALUE
+ CACHE 1;
+
+ALTER TABLE ONLY public."HostHistory" ALTER COLUMN history_revision_id
+ SET DEFAULT nextval('public."history_id_sequence"'::regclass);
diff --git a/db/src/main/resources/sql/schema/db-schema.sql.generated b/db/src/main/resources/sql/schema/db-schema.sql.generated
index 66bb2f7ce..093fe6500 100644
--- a/db/src/main/resources/sql/schema/db-schema.sql.generated
+++ b/db/src/main/resources/sql/schema/db-schema.sql.generated
@@ -11,6 +11,7 @@
-- 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.
+create sequence history_id_sequence start 1 increment 1;
create table "BillingCancellation" (
billing_cancellation_id bigserial not null,
@@ -208,6 +209,33 @@
primary key (id)
);
+ create table "HostHistory" (
+ history_revision_id int8 not null,
+ history_by_superuser boolean not null,
+ history_registrar_id text,
+ history_modification_time timestamptz not null,
+ history_reason text not null,
+ history_requested_by_registrar boolean not null,
+ history_client_transaction_id text,
+ history_server_transaction_id text,
+ history_type text not null,
+ history_xml_bytes bytea not null,
+ fully_qualified_host_name text,
+ inet_addresses text[],
+ last_superordinate_change timestamptz,
+ last_transfer_time timestamptz,
+ superordinate_domain text,
+ creation_registrar_id text not null,
+ creation_time timestamptz not null,
+ current_sponsor_registrar_id text not null,
+ deletion_time timestamptz,
+ last_epp_update_registrar_id text,
+ last_epp_update_time timestamptz,
+ statuses text[],
+ host_repo_id text not null,
+ primary key (history_revision_id)
+ );
+
create table "HostResource" (
repo_id text not null,
creation_registrar_id text not null,
@@ -403,6 +431,11 @@ create index IDXhsjqiy2lyobfymplb28nm74lm on "Domain" (current_sponsor_registrar
create index IDX5mnf0wn20tno4b9do88j61klr on "Domain" (deletion_time);
create index IDX1rcgkdd777bpvj0r94sltwd5y on "Domain" (fully_qualified_domain_name);
create index IDXrwl38wwkli1j7gkvtywi9jokq on "Domain" (tld);
+create index IDXfg2nnjlujxo6cb9fha971bq2n on "HostHistory" (creation_time);
+create index IDX1iy7njgb7wjmj9piml4l2g0qi on "HostHistory" (history_registrar_id);
+create index IDXj77pfwhui9f0i7wjq6lmibovj on "HostHistory" (fully_qualified_host_name);
+create index IDXknk8gmj7s47q56cwpa6rmpt5l on "HostHistory" (history_type);
+create index IDX67qwkjtlq5q8dv6egtrtnhqi7 on "HostHistory" (history_modification_time);
create index IDXe7wu46c7wpvfmfnj4565abibp on "PollMessage" (registrar_id);
create index IDXaydgox62uno9qx8cjlj5lauye on "PollMessage" (event_time);
create index premiumlist_name_idx on "PremiumList" (name);
diff --git a/db/src/main/resources/sql/schema/nomulus.golden.sql b/db/src/main/resources/sql/schema/nomulus.golden.sql
index 2b2905e1a..7c8ff86c2 100644
--- a/db/src/main/resources/sql/schema/nomulus.golden.sql
+++ b/db/src/main/resources/sql/schema/nomulus.golden.sql
@@ -325,6 +325,49 @@ CREATE TABLE public."DomainHost" (
);
+--
+-- Name: history_id_sequence; Type: SEQUENCE; Schema: public; Owner: -
+--
+
+CREATE SEQUENCE public.history_id_sequence
+ START WITH 1
+ INCREMENT BY 1
+ NO MINVALUE
+ NO MAXVALUE
+ CACHE 1;
+
+
+--
+-- Name: HostHistory; Type: TABLE; Schema: public; Owner: -
+--
+
+CREATE TABLE public."HostHistory" (
+ history_revision_id bigint DEFAULT nextval('public.history_id_sequence'::regclass) NOT NULL,
+ history_by_superuser boolean NOT NULL,
+ history_registrar_id text NOT NULL,
+ history_modification_time timestamp with time zone NOT NULL,
+ history_reason text NOT NULL,
+ history_requested_by_registrar boolean NOT NULL,
+ history_client_transaction_id text,
+ history_server_transaction_id text,
+ history_type text NOT NULL,
+ history_xml_bytes bytea NOT NULL,
+ fully_qualified_host_name text,
+ inet_addresses text[],
+ last_superordinate_change timestamp with time zone,
+ last_transfer_time timestamp with time zone,
+ superordinate_domain text,
+ creation_registrar_id text NOT NULL,
+ creation_time timestamp with time zone NOT NULL,
+ current_sponsor_registrar_id text NOT NULL,
+ deletion_time timestamp with time zone,
+ last_epp_update_registrar_id text,
+ last_epp_update_time timestamp with time zone,
+ statuses text[],
+ host_repo_id text NOT NULL
+);
+
+
--
-- Name: HostResource; Type: TABLE; Schema: public; Owner: -
--
@@ -733,6 +776,14 @@ ALTER TABLE ONLY public."Domain"
ADD CONSTRAINT "Domain_pkey" PRIMARY KEY (repo_id);
+--
+-- Name: HostHistory HostHistory_pkey; Type: CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY public."HostHistory"
+ ADD CONSTRAINT "HostHistory_pkey" PRIMARY KEY (history_revision_id);
+
+
--
-- Name: HostResource HostResource_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--
@@ -829,6 +880,13 @@ ALTER TABLE ONLY public."Contact"
ADD CONSTRAINT ukoqd7n4hbx86hvlgkilq75olas UNIQUE (contact_id);
+--
+-- Name: idx1iy7njgb7wjmj9piml4l2g0qi; Type: INDEX; Schema: public; Owner: -
+--
+
+CREATE INDEX idx1iy7njgb7wjmj9piml4l2g0qi ON public."HostHistory" USING btree (history_registrar_id);
+
+
--
-- Name: idx1p3esngcwwu6hstyua6itn6ff; Type: INDEX; Schema: public; Owner: -
--
@@ -871,6 +929,13 @@ CREATE INDEX idx5mnf0wn20tno4b9do88j61klr ON public."Domain" USING btree (deleti
CREATE INDEX idx5yfbr88439pxw0v3j86c74fp8 ON public."BillingEvent" USING btree (event_time);
+--
+-- Name: idx67qwkjtlq5q8dv6egtrtnhqi7; Type: INDEX; Schema: public; Owner: -
+--
+
+CREATE INDEX idx67qwkjtlq5q8dv6egtrtnhqi7 ON public."HostHistory" USING btree (history_modification_time);
+
+
--
-- Name: idx6py6ocrab0ivr76srcd2okpnq; Type: INDEX; Schema: public; Owner: -
--
@@ -941,6 +1006,13 @@ CREATE INDEX idxe7wu46c7wpvfmfnj4565abibp ON public."PollMessage" USING btree (r
CREATE INDEX idxeokttmxtpq2hohcioe5t2242b ON public."BillingCancellation" USING btree (registrar_id);
+--
+-- Name: idxfg2nnjlujxo6cb9fha971bq2n; Type: INDEX; Schema: public; Owner: -
+--
+
+CREATE INDEX idxfg2nnjlujxo6cb9fha971bq2n ON public."HostHistory" USING btree (creation_time);
+
+
--
-- Name: idxhmv411mdqo5ibn4vy7ykxpmlv; Type: INDEX; Schema: public; Owner: -
--
@@ -948,6 +1020,13 @@ CREATE INDEX idxeokttmxtpq2hohcioe5t2242b ON public."BillingCancellation" USING
CREATE INDEX idxhmv411mdqo5ibn4vy7ykxpmlv ON public."BillingEvent" USING btree (allocation_token_id);
+--
+-- Name: idxj77pfwhui9f0i7wjq6lmibovj; Type: INDEX; Schema: public; Owner: -
+--
+
+CREATE INDEX idxj77pfwhui9f0i7wjq6lmibovj ON public."HostHistory" USING btree (fully_qualified_host_name);
+
+
--
-- Name: idxjny8wuot75b5e6p38r47wdawu; Type: INDEX; Schema: public; Owner: -
--
@@ -962,6 +1041,13 @@ CREATE INDEX idxjny8wuot75b5e6p38r47wdawu ON public."BillingRecurrence" USING bt
CREATE INDEX idxkjt9yaq92876dstimd93hwckh ON public."Domain" USING btree (current_sponsor_registrar_id);
+--
+-- Name: idxknk8gmj7s47q56cwpa6rmpt5l; Type: INDEX; Schema: public; Owner: -
+--
+
+CREATE INDEX idxknk8gmj7s47q56cwpa6rmpt5l ON public."HostHistory" USING btree (history_type);
+
+
--
-- Name: idxn1f711wicdnooa2mqb7g1m55o; Type: INDEX; Schema: public; Owner: -
--
@@ -1071,6 +1157,14 @@ ALTER TABLE ONLY public."Domain"
ADD CONSTRAINT fk2u3srsfbei272093m3b3xwj23 FOREIGN KEY (current_sponsor_registrar_id) REFERENCES public."Registrar"(registrar_id);
+--
+-- Name: HostHistory fk3d09knnmxrt6iniwnp8j2ykga; Type: FK CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY public."HostHistory"
+ ADD CONSTRAINT fk3d09knnmxrt6iniwnp8j2ykga FOREIGN KEY (history_registrar_id) REFERENCES public."Registrar"(registrar_id);
+
+
--
-- Name: ClaimsEntry fk6sc6at5hedffc0nhdcab6ivuq; Type: FK CONSTRAINT; Schema: public; Owner: -
--
@@ -1239,6 +1333,14 @@ ALTER TABLE ONLY public."HostResource"
ADD CONSTRAINT fk_host_resource_superordinate_domain FOREIGN KEY (superordinate_domain) REFERENCES public."Domain"(repo_id);
+--
+-- Name: HostHistory fk_hosthistory_hostresource; Type: FK CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY public."HostHistory"
+ ADD CONSTRAINT fk_hosthistory_hostresource FOREIGN KEY (host_repo_id) REFERENCES public."HostResource"(repo_id);
+
+
--
-- Name: PollMessage fk_poll_message_contact_repo_id; Type: FK CONSTRAINT; Schema: public; Owner: -
--