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
This commit is contained in:
gbrodman 2020-06-16 11:47:17 -04:00 committed by GitHub
parent 56c9e81bcd
commit 58c557d715
15 changed files with 742 additions and 203 deletions

View file

@ -48,6 +48,8 @@ import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.stream.StreamSupport; import java.util.stream.StreamSupport;
import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.Column; import javax.persistence.Column;
import javax.persistence.MappedSuperclass; import javax.persistence.MappedSuperclass;
import javax.persistence.Transient; import javax.persistence.Transient;
@ -56,15 +58,21 @@ import org.joda.time.Duration;
/** An EPP entity object (i.e. a domain, contact, or host). */ /** An EPP entity object (i.e. a domain, contact, or host). */
@MappedSuperclass @MappedSuperclass
@Access(AccessType.FIELD) // otherwise it'll use the default if the repoId (property)
public abstract class EppResource extends BackupGroupRoot implements Buildable { public abstract class EppResource extends BackupGroupRoot implements Buildable {
/** /**
* Unique identifier in the registry for this resource. * Unique identifier in the registry for this resource.
* *
* <p>This is in the (\w|_){1,80}-\w{1,8} format specified by RFC 5730 for roidType. * <p>This is in the (\w|_){1,80}-\w{1,8} format specified by RFC 5730 for roidType.
*
* @see <a href="https://tools.ietf.org/html/rfc5730">RFC 5730</a> * @see <a href="https://tools.ietf.org/html/rfc5730">RFC 5730</a>
*/ */
@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. */ /** The ID of the registrar that is currently sponsoring this resource. */
@Index @Index
@ -138,6 +146,12 @@ public abstract class EppResource extends BackupGroupRoot implements Buildable {
return repoId; 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() { public final DateTime getCreationTime() {
return creationTime.getTimestamp(); return creationTime.getTimestamp();
} }
@ -214,7 +228,7 @@ public abstract class EppResource extends BackupGroupRoot implements Buildable {
} }
/** Abstract builder for {@link EppResource} types. */ /** Abstract builder for {@link EppResource} types. */
public abstract static class Builder<T extends EppResource, B extends Builder<?, ?>> public abstract static class Builder<T extends EppResource, B extends Builder<T, B>>
extends GenericBuilder<T, B> { extends GenericBuilder<T, B> {
/** Create a {@link Builder} wrapping a new instance. */ /** Create a {@link Builder} wrapping a new instance. */

View file

@ -37,6 +37,8 @@ import google.registry.schema.replay.DatastoreAndSqlEntity;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Stream; import java.util.stream.Stream;
import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.AttributeOverride; import javax.persistence.AttributeOverride;
import javax.persistence.AttributeOverrides; import javax.persistence.AttributeOverrides;
import javax.persistence.Column; import javax.persistence.Column;
@ -63,6 +65,7 @@ import org.joda.time.DateTime;
}) })
@ExternalMessagingName("contact") @ExternalMessagingName("contact")
@WithStringVKey @WithStringVKey
@Access(AccessType.FIELD)
public class ContactResource extends EppResource public class ContactResource extends EppResource
implements DatastoreAndSqlEntity, ForeignKeyedEppResource, ResourceWithTransferData { implements DatastoreAndSqlEntity, ForeignKeyedEppResource, ResourceWithTransferData {
@ -201,6 +204,13 @@ public class ContactResource extends EppResource
return VKey.createOfy(ContactResource.class, Key.create(this)); return VKey.createOfy(ContactResource.class, Key.create(this));
} }
@Override
@javax.persistence.Id
@Access(AccessType.PROPERTY)
public String getRepoId() {
return super.getRepoId();
}
public String getContactId() { public String getContactId() {
return contactId; return contactId;
} }

View file

@ -75,6 +75,8 @@ import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.function.Predicate; import java.util.function.Predicate;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.AttributeOverride; import javax.persistence.AttributeOverride;
import javax.persistence.AttributeOverrides; import javax.persistence.AttributeOverrides;
import javax.persistence.Column; import javax.persistence.Column;
@ -109,6 +111,7 @@ import org.joda.time.Interval;
}) })
@WithStringVKey @WithStringVKey
@ExternalMessagingName("domain") @ExternalMessagingName("domain")
@Access(AccessType.FIELD)
public class DomainBase extends EppResource public class DomainBase extends EppResource
implements DatastoreAndSqlEntity, implements DatastoreAndSqlEntity,
ForeignKeyedEppResource, ForeignKeyedEppResource,
@ -295,6 +298,13 @@ public class DomainBase extends EppResource
allContacts = contactsBuilder.build(); allContacts = contactsBuilder.build();
} }
@Override
@javax.persistence.Id
@Access(AccessType.PROPERTY)
public String getRepoId() {
return super.getRepoId();
}
public ImmutableSet<String> getSubordinateHosts() { public ImmutableSet<String> getSubordinateHosts() {
return nullToEmptyImmutableCopy(subordinateHosts); return nullToEmptyImmutableCopy(subordinateHosts);
} }

View file

@ -22,6 +22,7 @@ import com.google.common.collect.ImmutableSet;
import google.registry.model.EppResource; import google.registry.model.EppResource;
import google.registry.model.contact.ContactResource; import google.registry.model.contact.ContactResource;
import google.registry.model.domain.DomainBase; import google.registry.model.domain.DomainBase;
import google.registry.model.host.HostBase;
import google.registry.model.host.HostResource; import google.registry.model.host.HostResource;
import google.registry.model.translators.EnumToAttributeAdapter.EppEnum; import google.registry.model.translators.EnumToAttributeAdapter.EppEnum;
import google.registry.model.translators.StatusValueAdapter; 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. */ /** Enum to help clearly list which resource types a status value is allowed to be present on. */
private enum AllowedOn { private enum AllowedOn {
ALL(ContactResource.class, DomainBase.class, HostResource.class), ALL(ContactResource.class, DomainBase.class, HostBase.class, HostResource.class),
NONE, NONE,
DOMAINS(DomainBase.class); DOMAINS(DomainBase.class);

View file

@ -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.
*
* <p>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.
*
* <p>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 <a href="https://tools.ietf.org/html/rfc5732">RFC 5732</a>
*/
@MappedSuperclass
@Embeddable
@Access(AccessType.FIELD)
public class HostBase extends EppResource {
/**
* Fully qualified hostname, which is a unique identifier for this host.
*
* <p>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<InetAddress> inetAddresses;
/** The superordinate domain of this host, or null if this is an external host. */
@Index
@IgnoreSave(IfNull.class)
@DoNotHydrate
VKey<DomainBase> superordinateDomain;
/**
* The time that this resource was last transferred.
*
* <p>Can be null if the resource has never been transferred.
*/
DateTime lastTransferTime;
/**
* The most recent time that the {@link #superordinateDomain} field was changed.
*
* <p>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<DomainBase> getSuperordinateDomain() {
return superordinateDomain;
}
public boolean isSubordinate() {
return superordinateDomain != null;
}
public ImmutableSet<InetAddress> 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.
*
* <p>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.
*
* <p>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<T extends HostBase, B extends Builder<T, B>>
extends EppResource.Builder<T, B> {
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<InetAddress> inetAddresses) {
getInstance().inetAddresses = inetAddresses;
return thisCastToDerived();
}
public B setLastSuperordinateChange(DateTime lastSuperordinateChange) {
getInstance().lastSuperordinateChange = lastSuperordinateChange;
return thisCastToDerived();
}
public B addInetAddresses(ImmutableSet<InetAddress> inetAddresses) {
return setInetAddresses(
ImmutableSet.copyOf(union(getInstance().getInetAddresses(), inetAddresses)));
}
public B removeInetAddresses(ImmutableSet<InetAddress> inetAddresses) {
return setInetAddresses(
ImmutableSet.copyOf(difference(getInstance().getInetAddresses(), inetAddresses)));
}
public B setSuperordinateDomain(VKey<DomainBase> superordinateDomain) {
getInstance().superordinateDomain = superordinateDomain;
return thisCastToDerived();
}
public B setLastTransferTime(DateTime lastTransferTime) {
getInstance().lastTransferTime = lastTransferTime;
return thisCastToDerived();
}
}
}

View file

@ -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.
*
* <p>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<HostResource> 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<HostResource> getHostRepoId() {
return hostRepoId;
}
@Override
public Builder asBuilder() {
return new Builder(clone(this));
}
public static class Builder extends HistoryEntry.Builder<HostHistory, Builder> {
public Builder() {}
public Builder(HostHistory instance) {
super(instance);
}
public Builder setHostBase(HostBase hostBase) {
getInstance().hostBase = hostBase;
return this;
}
public Builder setHostResourceId(VKey<HostResource> 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<HostResource>) parent);
return this;
}
}
}

View file

@ -14,112 +14,36 @@
package google.registry.model.host; 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.Key;
import com.googlecode.objectify.annotation.Entity; 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.EppResource.ForeignKeyedEppResource;
import google.registry.model.annotations.ExternalMessagingName; import google.registry.model.annotations.ExternalMessagingName;
import google.registry.model.annotations.ReportedOn; 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.VKey;
import google.registry.persistence.WithStringVKey; import google.registry.persistence.WithStringVKey;
import google.registry.schema.replay.DatastoreAndSqlEntity; import google.registry.schema.replay.DatastoreAndSqlEntity;
import java.net.InetAddress; import javax.persistence.Access;
import java.util.Optional; import javax.persistence.AccessType;
import java.util.Set;
import javax.annotation.Nullable;
import org.joda.time.DateTime;
/** /**
* A persistable Host resource including mutable and non-mutable fields. * A persistable Host resource including mutable and non-mutable fields.
* *
* <p>A host's {@link TransferData} is stored on the superordinate domain. Non-subordinate hosts * <p>The {@link javax.persistence.Id} of the HostResource is the repoId.
* don't carry a full set of TransferData; all they have is lastTransferTime.
*
* @see <a href="https://tools.ietf.org/html/rfc5732">RFC 5732</a>
*/ */
@ReportedOn @ReportedOn
@Entity @Entity
@javax.persistence.Entity @javax.persistence.Entity
@ExternalMessagingName("host") @ExternalMessagingName("host")
@WithStringVKey @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 { implements DatastoreAndSqlEntity, ForeignKeyedEppResource {
/**
* Fully qualified hostname, which is a unique identifier for this host.
*
* <p>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<InetAddress> inetAddresses;
/** The superordinate domain of this host, or null if this is an external host. */
@Index
@IgnoreSave(IfNull.class)
@DoNotHydrate
VKey<DomainBase> superordinateDomain;
/**
* The time that this resource was last transferred.
*
* <p>Can be null if the resource has never been transferred.
*/
DateTime lastTransferTime;
/**
* The most recent time that the {@link #superordinateDomain} field was changed.
*
* <p>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<DomainBase> getSuperordinateDomain() {
return superordinateDomain;
}
public boolean isSubordinate() {
return superordinateDomain != null;
}
public ImmutableSet<InetAddress> getInetAddresses() {
return nullToEmptyImmutableCopy(inetAddresses);
}
public DateTime getLastTransferTime() {
return lastTransferTime;
}
public DateTime getLastSuperordinateChange() {
return lastSuperordinateChange;
}
@Override @Override
public String getForeignKey() { @javax.persistence.Id
return fullyQualifiedHostName; @Access(AccessType.PROPERTY) // to tell it to use the non-default property-as-ID
public String getRepoId() {
return super.getRepoId();
} }
@Override @Override
@ -128,92 +52,17 @@ public class HostResource extends EppResource
return VKey.createOfy(HostResource.class, Key.create(this)); 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.
*
* <p>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.
*
* <p>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 @Override
public Builder asBuilder() { public Builder asBuilder() {
return new Builder(clone(this)); return new Builder(clone(this));
} }
/** A builder for constructing {@link HostResource}, since it is immutable. */ /** A builder for constructing {@link HostResource}, since it is immutable. */
public static class Builder extends EppResource.Builder<HostResource, Builder> { public static class Builder extends HostBase.Builder<HostResource, Builder> {
public Builder() {} public Builder() {}
private Builder(HostResource instance) { private Builder(HostResource instance) {
super(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<InetAddress> inetAddresses) {
getInstance().inetAddresses = inetAddresses;
return this;
}
public Builder setLastSuperordinateChange(DateTime lastSuperordinateChange) {
getInstance().lastSuperordinateChange = lastSuperordinateChange;
return this;
}
public Builder addInetAddresses(ImmutableSet<InetAddress> inetAddresses) {
return setInetAddresses(ImmutableSet.copyOf(
union(getInstance().getInetAddresses(), inetAddresses)));
}
public Builder removeInetAddresses(ImmutableSet<InetAddress> inetAddresses) {
return setInetAddresses(ImmutableSet.copyOf(
difference(getInstance().getInetAddresses(), inetAddresses)));
}
public Builder setSuperordinateDomain(VKey<DomainBase> superordinateDomain) {
getInstance().superordinateDomain = superordinateDomain;
return this;
}
public Builder setLastTransferTime(DateTime lastTransferTime) {
getInstance().lastTransferTime = lastTransferTime;
return this;
}
} }
} }

View file

@ -32,11 +32,22 @@ import google.registry.model.domain.Period;
import google.registry.model.eppcommon.Trid; import google.registry.model.eppcommon.Trid;
import java.util.Set; import java.util.Set;
import javax.annotation.Nullable; 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; import org.joda.time.DateTime;
/** A record of an EPP command that mutated a resource. */ /** A record of an EPP command that mutated a resource. */
@ReportedOn @ReportedOn
@Entity @Entity
@MappedSuperclass
public class HistoryEntry extends ImmutableObject implements Buildable { public class HistoryEntry extends ImmutableObject implements Buildable {
/** Represents the type of history entry. */ /** Represents the type of history entry. */
@ -60,8 +71,8 @@ public class HistoryEntry extends ImmutableObject implements Buildable {
@Deprecated @Deprecated
DOMAIN_ALLOCATE, DOMAIN_ALLOCATE,
/** /**
* Used for domain registration autorenews explicitly logged by * Used for domain registration autorenews explicitly logged by {@link
* {@link google.registry.batch.ExpandRecurringBillingEventsAction}. * google.registry.batch.ExpandRecurringBillingEventsAction}.
*/ */
DOMAIN_AUTORENEW, DOMAIN_AUTORENEW,
DOMAIN_CREATE, DOMAIN_CREATE,
@ -89,14 +100,22 @@ public class HistoryEntry extends ImmutableObject implements Buildable {
} }
/** The autogenerated id of this event. */ /** The autogenerated id of this event. */
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "HistorySequenceGenerator")
@SequenceGenerator(
name = "HistorySequenceGenerator",
sequenceName = "history_id_sequence",
allocationSize = 1)
@Id @Id
long id; @javax.persistence.Id
@Column(name = "historyRevisionId")
Long id;
/** The resource this event mutated. */ /** The resource this event mutated. */
@Parent @Parent @Transient protected Key<? extends EppResource> parent;
Key<? extends EppResource> parent;
/** The type of history entry. */ /** The type of history entry. */
@Column(nullable = false, name = "historyType")
@Enumerated(EnumType.STRING)
Type type; Type type;
/** /**
@ -104,17 +123,21 @@ public class HistoryEntry extends ImmutableObject implements Buildable {
* be null for all other types. * be null for all other types.
*/ */
@IgnoreSave(IfNull.class) @IgnoreSave(IfNull.class)
@Transient // domain-specific
Period period; Period period;
/** The actual EPP xml of the command, stored as bytes to be agnostic of encoding. */ /** The actual EPP xml of the command, stored as bytes to be agnostic of encoding. */
@Column(nullable = false, name = "historyXmlBytes")
byte[] xmlBytes; 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 @Index
@Column(nullable = false, name = "historyModificationTime")
DateTime modificationTime; DateTime modificationTime;
/** The id of the registrar that sent the command. */ /** The id of the registrar that sent the command. */
@Index @Index
@Column(name = "historyRegistrarId")
String clientId; 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 * sending the EPP transfer command is the gaining party). For approves and rejects, the other
* registrar is the gaining party. * registrar is the gaining party.
*/ */
@Transient // domain-specific
String otherClientId; String otherClientId;
/** Transaction id that made this change, or null if the entry was not created by a flow. */ /** 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. */ /** Whether this change was created by a superuser. */
@Column(nullable = false, name = "historyBySuperuser")
boolean bySuperuser; boolean bySuperuser;
/** Reason for the change. */ /** Reason for the change. */
@Column(nullable = false, name = "historyReason")
String reason; String reason;
/** Whether this change was requested by a registrar. */ /** Whether this change was requested by a registrar. */
@Column(nullable = false, name = "historyRequestedByRegistrar")
Boolean requestedByRegistrar; 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 * also be empty if the HistoryEntry refers to an EPP mutation that does not affect domain
* transaction counts (such as contact or host mutations). * transaction counts (such as contact or host mutations).
*/ */
@Transient // domain-specific
Set<DomainTransactionRecord> domainTransactionRecords; Set<DomainTransactionRecord> domainTransactionRecords;
public Key<? extends EppResource> getParent() { 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. */ /** 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; return trid;
} }
@ -202,77 +240,84 @@ public class HistoryEntry extends ImmutableObject implements Buildable {
} }
/** A builder for {@link HistoryEntry} since it is immutable */ /** A builder for {@link HistoryEntry} since it is immutable */
public static class Builder extends Buildable.Builder<HistoryEntry> { public static class Builder<T extends HistoryEntry, B extends Builder<?, ?>>
extends GenericBuilder<T, B> {
public Builder() {} public Builder() {}
public Builder(HistoryEntry instance) { public Builder(T instance) {
super(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); getInstance().parent = Key.create(parent);
return this; return thisCastToDerived();
} }
public Builder setParent(Key<? extends EppResource> parentKey) { // Until we move completely to SQL, override this in subclasses (e.g. HostHistory) to set VKeys
getInstance().parent = parentKey; public B setParent(Key<? extends EppResource> parent) {
return this; getInstance().parent = parent;
return thisCastToDerived();
} }
public Builder setType(Type type) { public B setType(Type type) {
getInstance().type = type; getInstance().type = type;
return this; return thisCastToDerived();
} }
public Builder setPeriod(Period period) { public B setPeriod(Period period) {
getInstance().period = period; getInstance().period = period;
return this; return thisCastToDerived();
} }
public Builder setXmlBytes(byte[] xmlBytes) { public B setXmlBytes(byte[] xmlBytes) {
getInstance().xmlBytes = xmlBytes; getInstance().xmlBytes = xmlBytes;
return this; return thisCastToDerived();
} }
public Builder setModificationTime(DateTime modificationTime) { public B setModificationTime(DateTime modificationTime) {
getInstance().modificationTime = modificationTime; getInstance().modificationTime = modificationTime;
return this; return thisCastToDerived();
} }
public Builder setClientId(String clientId) { public B setClientId(String clientId) {
getInstance().clientId = clientId; getInstance().clientId = clientId;
return this; return thisCastToDerived();
} }
public Builder setOtherClientId(String otherClientId) { public B setOtherClientId(String otherClientId) {
getInstance().otherClientId = otherClientId; getInstance().otherClientId = otherClientId;
return this; return thisCastToDerived();
} }
public Builder setTrid(Trid trid) { public B setTrid(Trid trid) {
getInstance().trid = trid; getInstance().trid = trid;
return this; return thisCastToDerived();
} }
public Builder setBySuperuser(boolean bySuperuser) { public B setBySuperuser(boolean bySuperuser) {
getInstance().bySuperuser = bySuperuser; getInstance().bySuperuser = bySuperuser;
return this; return thisCastToDerived();
} }
public Builder setReason(String reason) { public B setReason(String reason) {
getInstance().reason = reason; getInstance().reason = reason;
return this; return thisCastToDerived();
} }
public Builder setRequestedByRegistrar(Boolean requestedByRegistrar) { public B setRequestedByRegistrar(Boolean requestedByRegistrar) {
getInstance().requestedByRegistrar = requestedByRegistrar; getInstance().requestedByRegistrar = requestedByRegistrar;
return this; return thisCastToDerived();
} }
public Builder setDomainTransactionRecords( public B setDomainTransactionRecords(
ImmutableSet<DomainTransactionRecord> domainTransactionRecords) { ImmutableSet<DomainTransactionRecord> domainTransactionRecords) {
getInstance().domainTransactionRecords = domainTransactionRecords; getInstance().domainTransactionRecords = domainTransactionRecords;
return this; return thisCastToDerived();
} }
} }
} }

View file

@ -24,6 +24,7 @@
<class>google.registry.model.billing.BillingEvent$Recurring</class> <class>google.registry.model.billing.BillingEvent$Recurring</class>
<class>google.registry.model.contact.ContactResource</class> <class>google.registry.model.contact.ContactResource</class>
<class>google.registry.model.domain.DomainBase</class> <class>google.registry.model.domain.DomainBase</class>
<class>google.registry.model.host.HostHistory</class>
<class>google.registry.model.host.HostResource</class> <class>google.registry.model.host.HostResource</class>
<class>google.registry.model.registrar.Registrar</class> <class>google.registry.model.registrar.Registrar</class>
<class>google.registry.model.registrar.RegistrarContact</class> <class>google.registry.model.registrar.RegistrarContact</class>
@ -63,8 +64,8 @@
<!-- Generated converters for VKey --> <!-- Generated converters for VKey -->
<class>google.registry.model.billing.VKeyConverter_BillingEvent</class> <class>google.registry.model.billing.VKeyConverter_BillingEvent</class>
<class>google.registry.model.domain.VKeyConverter_DomainBase</class>
<class>google.registry.model.contact.VKeyConverter_ContactResource</class> <class>google.registry.model.contact.VKeyConverter_ContactResource</class>
<class>google.registry.model.domain.VKeyConverter_DomainBase</class>
<class>google.registry.model.domain.token.VKeyConverter_AllocationToken</class> <class>google.registry.model.domain.token.VKeyConverter_AllocationToken</class>
<class>google.registry.model.host.VKeyConverter_HostResource</class> <class>google.registry.model.host.VKeyConverter_HostResource</class>
<class>google.registry.model.poll.VKeyConverter_Autorenew</class> <class>google.registry.model.poll.VKeyConverter_Autorenew</class>

View file

@ -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<HostResource> hostVKey = VKey.createSql(HostResource.class, "host1");
HostResource hostFromDb = jpaTm().transact(() -> jpaTm().load(hostVKey));
HostHistory hostHistory =
new HostHistory.Builder()
.setType(HistoryEntry.Type.HOST_CREATE)
.setXmlBytes("<xml></xml>".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());
}
}

View file

@ -19,6 +19,7 @@ import static com.google.common.truth.Truth.assert_;
import google.registry.model.billing.BillingEventTest; import google.registry.model.billing.BillingEventTest;
import google.registry.model.contact.ContactResourceTest; import google.registry.model.contact.ContactResourceTest;
import google.registry.model.domain.DomainBaseSqlTest; import google.registry.model.domain.DomainBaseSqlTest;
import google.registry.model.history.HostHistoryTest;
import google.registry.model.poll.PollMessageTest; import google.registry.model.poll.PollMessageTest;
import google.registry.model.registry.RegistryLockDaoTest; import google.registry.model.registry.RegistryLockDaoTest;
import google.registry.persistence.transaction.JpaEntityCoverage; import google.registry.persistence.transaction.JpaEntityCoverage;
@ -75,6 +76,7 @@ import org.junit.runner.RunWith;
ContactResourceTest.class, ContactResourceTest.class,
CursorDaoTest.class, CursorDaoTest.class,
DomainBaseSqlTest.class, DomainBaseSqlTest.class,
HostHistoryTest.class,
LockDaoTest.class, LockDaoTest.class,
PollMessageTest.class, PollMessageTest.class,
PremiumListDaoTest.class, PremiumListDaoTest.class,

View file

@ -618,7 +618,7 @@ enum google.registry.model.reporting.DomainTransactionRecord$TransactionReportFi
TRANSFER_SUCCESSFUL; TRANSFER_SUCCESSFUL;
} }
class google.registry.model.reporting.HistoryEntry { 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; @Parent com.googlecode.objectify.Key<? extends google.registry.model.EppResource> parent;
boolean bySuperuser; boolean bySuperuser;
byte[] xmlBytes; byte[] xmlBytes;

View file

@ -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);

View file

@ -11,6 +11,7 @@
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and -- See the License for the specific language governing permissions and
-- limitations under the License. -- limitations under the License.
create sequence history_id_sequence start 1 increment 1;
create table "BillingCancellation" ( create table "BillingCancellation" (
billing_cancellation_id bigserial not null, billing_cancellation_id bigserial not null,
@ -208,6 +209,33 @@
primary key (id) 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" ( create table "HostResource" (
repo_id text not null, repo_id text not null,
creation_registrar_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 IDX5mnf0wn20tno4b9do88j61klr on "Domain" (deletion_time);
create index IDX1rcgkdd777bpvj0r94sltwd5y on "Domain" (fully_qualified_domain_name); create index IDX1rcgkdd777bpvj0r94sltwd5y on "Domain" (fully_qualified_domain_name);
create index IDXrwl38wwkli1j7gkvtywi9jokq on "Domain" (tld); 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 IDXe7wu46c7wpvfmfnj4565abibp on "PollMessage" (registrar_id);
create index IDXaydgox62uno9qx8cjlj5lauye on "PollMessage" (event_time); create index IDXaydgox62uno9qx8cjlj5lauye on "PollMessage" (event_time);
create index premiumlist_name_idx on "PremiumList" (name); create index premiumlist_name_idx on "PremiumList" (name);

View file

@ -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: - -- Name: HostResource; Type: TABLE; Schema: public; Owner: -
-- --
@ -733,6 +776,14 @@ ALTER TABLE ONLY public."Domain"
ADD CONSTRAINT "Domain_pkey" PRIMARY KEY (repo_id); 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: - -- Name: HostResource HostResource_pkey; Type: CONSTRAINT; Schema: public; Owner: -
-- --
@ -829,6 +880,13 @@ ALTER TABLE ONLY public."Contact"
ADD CONSTRAINT ukoqd7n4hbx86hvlgkilq75olas UNIQUE (contact_id); 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: - -- 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); 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: - -- 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); 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: - -- 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); 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: - -- 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); 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: - -- 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); 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: - -- 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); 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: - -- Name: PollMessage fk_poll_message_contact_repo_id; Type: FK CONSTRAINT; Schema: public; Owner: -
-- --