From 47178d4fb5e54e55fb6fb030f9903634c0768a04 Mon Sep 17 00:00:00 2001 From: gbrodman Date: Tue, 16 Jun 2020 11:47:17 -0400 Subject: [PATCH] Add HostBase and HostHistory classes (#587) * Add proof of concept for HostBase and HostHistory classes * Use a PROPERTY accessor for @Ids * Add an unused setter method for Hibernate's sake * Refactor HostHistory * Some responses to CR * Fix relationship and test * Manually manage the foreign keys for HostHistory * Protect HostBase's builder and use text for the enum type * Add responses to CR - Add javadocs - Create an ID sequence for host history objects * Don't try to set the ID * Use a Long and remove the setter * Add some comments and rename a couple fields * Don't change Datastore schema * Use Long in the Datastore schema * Add new createVKey method * Add comments and rename fields * Rename v27->v31 and regenerate the golden * Fix superordinateDomain and inetAddresses in HostHistory * V31 -> V32 * Fix SQL files that got messed up in the merge * Configure and use a manually-created history ID sequence * Add three more indices to HostHistory --- .../google/registry/model/EppResource.java | 18 +- .../model/contact/ContactResource.java | 10 + .../registry/model/domain/DomainBase.java | 10 + .../registry/model/eppcommon/StatusValue.java | 3 +- .../google/registry/model/host/HostBase.java | 229 ++++++++++++++++++ .../registry/model/host/HostHistory.java | 90 +++++++ .../registry/model/host/HostResource.java | 171 +------------ .../model/reporting/HistoryEntry.java | 119 ++++++--- .../main/resources/META-INF/persistence.xml | 3 +- .../model/history/HostHistoryTest.java | 87 +++++++ .../integration/SqlIntegrationTestSuite.java | 2 + .../google/registry/model/schema.txt | 2 +- .../sql/flyway/V32__create_host_history.sql | 66 +++++ .../sql/schema/db-schema.sql.generated | 33 +++ .../resources/sql/schema/nomulus.golden.sql | 102 ++++++++ 15 files changed, 742 insertions(+), 203 deletions(-) create mode 100644 core/src/main/java/google/registry/model/host/HostBase.java create mode 100644 core/src/main/java/google/registry/model/host/HostHistory.java create mode 100644 core/src/test/java/google/registry/model/history/HostHistoryTest.java create mode 100644 db/src/main/resources/sql/flyway/V32__create_host_history.sql 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 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 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 parent; + @Parent @Transient protected Key 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 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 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 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 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: - --