From b34da92f420f62db4beefcbc23b8778b79b921ca Mon Sep 17 00:00:00 2001 From: Shicong Huang Date: Thu, 3 Sep 2020 10:21:23 -0400 Subject: [PATCH] Use composite primary key for DomainHistory (#767) * Use composite primary key for DomainHistory * Move History table's SequenceGenerator to orm.xml * Rebase on HEAD and remove default value for key in History tables * Use primitive type for id. * Revert the cache change --- .../model/contact/ContactHistory.java | 15 +++ .../registry/model/domain/DomainHistory.java | 105 ++++++++++++++---- .../registry/model/host/HostHistory.java | 15 +++ .../model/reporting/HistoryEntry.java | 44 ++++---- core/src/main/resources/META-INF/orm.xml | 3 + .../model/history/DomainHistoryTest.java | 13 ++- .../google/registry/model/schema.txt | 2 +- ...e_primary_key_for_domain_history_table.sql | 34 ++++++ .../sql/schema/db-schema.sql.generated | 15 +-- .../resources/sql/schema/nomulus.golden.sql | 51 ++++----- 10 files changed, 219 insertions(+), 78 deletions(-) create mode 100644 db/src/main/resources/sql/flyway/V51__use_composite_primary_key_for_domain_history_table.sql diff --git a/core/src/main/java/google/registry/model/contact/ContactHistory.java b/core/src/main/java/google/registry/model/contact/ContactHistory.java index 9160061cf..3fae69b76 100644 --- a/core/src/main/java/google/registry/model/contact/ContactHistory.java +++ b/core/src/main/java/google/registry/model/contact/ContactHistory.java @@ -19,8 +19,13 @@ import com.googlecode.objectify.annotation.EntitySubclass; import google.registry.model.EppResource; import google.registry.model.reporting.HistoryEntry; import google.registry.persistence.VKey; +import javax.persistence.Access; +import javax.persistence.AccessType; import javax.persistence.Column; import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; /** * A persisted history entry representing an EPP modification to a contact. @@ -38,6 +43,7 @@ import javax.persistence.Entity; @javax.persistence.Index(columnList = "historyModificationTime") }) @EntitySubclass +@Access(AccessType.FIELD) public class ContactHistory extends HistoryEntry { // Store ContactBase instead of ContactResource so we don't pick up its @Id ContactBase contactBase; @@ -45,6 +51,15 @@ public class ContactHistory extends HistoryEntry { @Column(nullable = false) VKey contactRepoId; + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "HistorySequenceGenerator") + @Column(name = "historyRevisionId") + @Access(AccessType.PROPERTY) + @Override + public long getId() { + return super.getId(); + } + /** The state of the {@link ContactBase} object at this point in time. */ public ContactBase getContactBase() { return contactBase; diff --git a/core/src/main/java/google/registry/model/domain/DomainHistory.java b/core/src/main/java/google/registry/model/domain/DomainHistory.java index f37771be9..0821ccceb 100644 --- a/core/src/main/java/google/registry/model/domain/DomainHistory.java +++ b/core/src/main/java/google/registry/model/domain/DomainHistory.java @@ -14,20 +14,32 @@ package google.registry.model.domain; +import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy; + import com.googlecode.objectify.Key; import com.googlecode.objectify.annotation.EntitySubclass; +import com.googlecode.objectify.annotation.Ignore; import google.registry.model.EppResource; -import google.registry.model.contact.ContactResource; +import google.registry.model.ImmutableObject; +import google.registry.model.domain.DomainHistory.DomainHistoryId; import google.registry.model.host.HostResource; import google.registry.model.reporting.HistoryEntry; import google.registry.persistence.VKey; +import java.io.Serializable; import java.util.Set; import javax.persistence.Access; import javax.persistence.AccessType; import javax.persistence.Column; import javax.persistence.ElementCollection; import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.IdClass; +import javax.persistence.Index; import javax.persistence.JoinTable; +import javax.persistence.PostLoad; +import javax.persistence.Table; /** * A persisted history entry representing an EPP modification to a domain. @@ -37,27 +49,43 @@ import javax.persistence.JoinTable; * the foreign-keyed fields in that class can refer to this object. */ @Entity -@javax.persistence.Table( +@Table( indexes = { - @javax.persistence.Index(columnList = "creationTime"), - @javax.persistence.Index(columnList = "historyRegistrarId"), - @javax.persistence.Index(columnList = "historyType"), - @javax.persistence.Index(columnList = "historyModificationTime") + @Index(columnList = "creationTime"), + @Index(columnList = "historyRegistrarId"), + @Index(columnList = "historyType"), + @Index(columnList = "historyModificationTime") }) @EntitySubclass +@Access(AccessType.FIELD) +@IdClass(DomainHistoryId.class) public class DomainHistory extends HistoryEntry { // Store DomainContent instead of DomainBase so we don't pick up its @Id DomainContent domainContent; - @Column(nullable = false) - VKey domainRepoId; + @Id String domainRepoId; + // We could have reused domainContent.nsHosts here, but Hibernate throws a weird exception after + // we change to use a composite primary key. + // TODO(b/166776754): Investigate if we can reuse domainContent.nsHosts for storing host keys. + @Ignore @ElementCollection @JoinTable(name = "DomainHistoryHost") - @Access(AccessType.PROPERTY) @Column(name = "host_repo_id") + Set> nsHosts; + + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "HistorySequenceGenerator") + @Column(name = "historyRevisionId") + @Access(AccessType.PROPERTY) + @Override + public long getId() { + return super.getId(); + } + + /** Returns keys to the {@link HostResource} that are the nameservers for the domain. */ public Set> getNsHosts() { - return domainContent.nsHosts; + return nsHosts; } /** The state of the {@link DomainContent} object at this point in time. */ @@ -65,16 +93,51 @@ public class DomainHistory extends HistoryEntry { return domainContent; } - /** The key to the {@link ContactResource} this is based off of. */ + /** The key to the {@link DomainBase} this is based off of. */ public VKey getDomainRepoId() { - return domainRepoId; + return VKey.create(DomainBase.class, domainRepoId, Key.create(DomainBase.class, domainRepoId)); } - // Hibernate needs this in order to populate nsHosts but no one else should ever use it - @SuppressWarnings("UnusedMethod") - private void setNsHosts(Set> nsHosts) { + public VKey createVKey() { + return VKey.createSql(DomainHistory.class, new DomainHistoryId(domainRepoId, getId())); + } + + @PostLoad + void postLoad() { if (domainContent != null) { - domainContent.nsHosts = nsHosts; + domainContent.nsHosts = nullToEmptyImmutableCopy(nsHosts); + } + } + + /** Class to represent the composite primary key of {@link DomainHistory} entity. */ + static class DomainHistoryId extends ImmutableObject implements Serializable { + + private String domainRepoId; + + private Long id; + + /** Hibernate requires this default constructor. */ + private DomainHistoryId() {} + + DomainHistoryId(String domainRepoId, long id) { + this.domainRepoId = domainRepoId; + this.id = id; + } + + String getDomainRepoId() { + return domainRepoId; + } + + void setDomainRepoId(String domainRepoId) { + this.domainRepoId = domainRepoId; + } + + long getId() { + return id; + } + + void setId(long id) { + this.id = id; } } @@ -93,12 +156,15 @@ public class DomainHistory extends HistoryEntry { public Builder setDomainContent(DomainContent domainContent) { getInstance().domainContent = domainContent; + if (domainContent != null) { + getInstance().nsHosts = nullToEmptyImmutableCopy(domainContent.nsHosts); + } return this; } - public Builder setDomainRepoId(VKey domainRepoId) { + public Builder setDomainRepoId(String domainRepoId) { getInstance().domainRepoId = domainRepoId; - domainRepoId.maybeGetOfyKey().ifPresent(parent -> getInstance().parent = parent); + getInstance().parent = Key.create(DomainBase.class, domainRepoId); return this; } @@ -106,8 +172,7 @@ public class DomainHistory extends HistoryEntry { @Override public Builder setParent(Key parent) { super.setParent(parent); - getInstance().domainRepoId = - VKey.create(DomainBase.class, parent.getName(), (Key) parent); + getInstance().domainRepoId = parent.getName(); return this; } } diff --git a/core/src/main/java/google/registry/model/host/HostHistory.java b/core/src/main/java/google/registry/model/host/HostHistory.java index e220ca1ad..5f883d96b 100644 --- a/core/src/main/java/google/registry/model/host/HostHistory.java +++ b/core/src/main/java/google/registry/model/host/HostHistory.java @@ -19,8 +19,13 @@ import com.googlecode.objectify.annotation.EntitySubclass; import google.registry.model.EppResource; import google.registry.model.reporting.HistoryEntry; import google.registry.persistence.VKey; +import javax.persistence.Access; +import javax.persistence.AccessType; import javax.persistence.Column; import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; /** * A persisted history entry representing an EPP modification to a host. @@ -39,6 +44,7 @@ import javax.persistence.Entity; @javax.persistence.Index(columnList = "historyModificationTime") }) @EntitySubclass +@Access(AccessType.FIELD) public class HostHistory extends HistoryEntry { // Store HostBase instead of HostResource so we don't pick up its @Id @@ -47,6 +53,15 @@ public class HostHistory extends HistoryEntry { @Column(nullable = false) VKey hostRepoId; + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "HistorySequenceGenerator") + @Column(name = "historyRevisionId") + @Access(AccessType.PROPERTY) + @Override + public long getId() { + return super.getId(); + } + /** The state of the {@link HostBase} object at this point in time. */ public HostBase getHostBase() { return hostBase; 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 1a6bc53e0..10a2986f4 100644 --- a/core/src/main/java/google/registry/model/reporting/HistoryEntry.java +++ b/core/src/main/java/google/registry/model/reporting/HistoryEntry.java @@ -42,15 +42,14 @@ import google.registry.persistence.VKey; import google.registry.persistence.WithStringVKey; import java.util.Set; import javax.annotation.Nullable; +import javax.persistence.Access; +import javax.persistence.AccessType; 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; @@ -59,6 +58,7 @@ import org.joda.time.DateTime; @Entity @MappedSuperclass @WithStringVKey // TODO(b/162229294): This should be resolved during the course of that bug +@Access(AccessType.FIELD) public class HistoryEntry extends ImmutableObject implements Buildable { /** Represents the type of history entry. */ @@ -110,17 +110,13 @@ public class HistoryEntry extends ImmutableObject implements Buildable { SYNTHETIC } - /** The autogenerated id of this event. */ - @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "HistorySequenceGenerator") - @SequenceGenerator( - name = "HistorySequenceGenerator", - sequenceName = "history_id_sequence", - allocationSize = 1) - @Id - @javax.persistence.Id - @Column(name = "historyRevisionId") - @VisibleForTesting - public Long id; + /** + * The autogenerated id of this event. Note that, this field is marked as {@link Transient} in the + * SQL schema, this is because the child class of {@link HistoryEntry}, e.g. {@link + * DomainHistory}, uses a composite primary key which the id is part of, and Hibernate requires + * that all the {@link javax.persistence.Id} fields must be put in the exact same class. + */ + @Id @Transient @VisibleForTesting public Long id; /** The resource this event mutated. */ @Parent @Transient protected Key parent; @@ -196,8 +192,17 @@ public class HistoryEntry extends ImmutableObject implements Buildable { @Transient // domain-specific Set domainTransactionRecords; - public Long getId() { - return id; + public long getId() { + // For some reason, Hibernate throws NPE during some initialization phase if we don't deal with + // the null case. Setting the id to 0L when it is null should be fine because 0L for primitive + // type is considered as null for wrapper class in the Hibernate context. + return id == null ? 0L : id; + } + + // This method is required by Hibernate. + @SuppressWarnings("UnusedMethod") + private void setId(long id) { + this.id = id; } public Key getParent() { @@ -279,11 +284,7 @@ public class HistoryEntry extends ImmutableObject implements Buildable { // can't use a switch statement since we're calling getKind() if (parentKind.equals(getKind(DomainBase.class))) { resultEntity = - new DomainHistory.Builder() - .copyFrom(this) - .setDomainRepoId( - VKey.create(DomainBase.class, parent.getName(), (Key) parent)) - .build(); + new DomainHistory.Builder().copyFrom(this).setDomainRepoId(parent.getName()).build(); } else if (parentKind.equals(getKind(HostResource.class))) { resultEntity = new HostHistory.Builder() @@ -349,6 +350,7 @@ public class HistoryEntry extends ImmutableObject implements Buildable { return thisCastToDerived(); } + // 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(); diff --git a/core/src/main/resources/META-INF/orm.xml b/core/src/main/resources/META-INF/orm.xml index 9eb506ffa..23c7235b1 100644 --- a/core/src/main/resources/META-INF/orm.xml +++ b/core/src/main/resources/META-INF/orm.xml @@ -10,6 +10,9 @@ + + + diff --git a/core/src/test/java/google/registry/model/history/DomainHistoryTest.java b/core/src/test/java/google/registry/model/history/DomainHistoryTest.java index 78998c22d..9fb520b10 100644 --- a/core/src/test/java/google/registry/model/history/DomainHistoryTest.java +++ b/core/src/test/java/google/registry/model/history/DomainHistoryTest.java @@ -14,6 +14,7 @@ package google.registry.model.history; +import static com.google.common.collect.ImmutableSet.toImmutableSet; import static com.google.common.truth.Truth.assertThat; import static google.registry.model.ImmutableObjectSubject.assertAboutImmutableObjects; import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm; @@ -71,11 +72,15 @@ public class DomainHistoryTest extends EntityTestCase { jpaTm() .transact( () -> { - DomainHistory fromDatabase = - jpaTm().load(VKey.createSql(DomainHistory.class, domainHistory.getId())); + DomainHistory fromDatabase = jpaTm().load(domainHistory.createVKey()); assertDomainHistoriesEqual(fromDatabase, domainHistory); assertThat(fromDatabase.getDomainRepoId().getSqlKey()) .isEqualTo(domainHistory.getDomainRepoId().getSqlKey()); + assertThat(fromDatabase.getNsHosts()) + .containsExactlyElementsIn( + domainHistory.getNsHosts().stream() + .map(key -> VKey.createSql(HostResource.class, key.getSqlKey())) + .collect(toImmutableSet())); }); } @@ -120,7 +125,7 @@ public class DomainHistoryTest extends EntityTestCase { static void assertDomainHistoriesEqual(DomainHistory one, DomainHistory two) { assertAboutImmutableObjects() .that(one) - .isEqualExceptFields(two, "domainContent", "domainRepoId", "parent"); + .isEqualExceptFields(two, "domainContent", "domainRepoId", "parent", "nsHosts"); } private DomainHistory createDomainHistory(DomainContent domain) { @@ -134,7 +139,7 @@ public class DomainHistoryTest extends EntityTestCase { .setReason("reason") .setRequestedByRegistrar(true) .setDomainContent(domain) - .setDomainRepoId(domain.createVKey()) + .setDomainRepoId(domain.getRepoId()) .build(); } } diff --git a/core/src/test/resources/google/registry/model/schema.txt b/core/src/test/resources/google/registry/model/schema.txt index a0da774cf..dea026cec 100644 --- a/core/src/test/resources/google/registry/model/schema.txt +++ b/core/src/test/resources/google/registry/model/schema.txt @@ -270,9 +270,9 @@ class google.registry.model.domain.DomainHistory { google.registry.model.domain.Period period; google.registry.model.eppcommon.Trid trid; google.registry.model.reporting.HistoryEntry$Type type; - google.registry.persistence.VKey domainRepoId; java.lang.Boolean requestedByRegistrar; java.lang.String clientId; + java.lang.String domainRepoId; java.lang.String otherClientId; java.lang.String reason; java.util.Set domainTransactionRecords; diff --git a/db/src/main/resources/sql/flyway/V51__use_composite_primary_key_for_domain_history_table.sql b/db/src/main/resources/sql/flyway/V51__use_composite_primary_key_for_domain_history_table.sql new file mode 100644 index 000000000..3bc306837 --- /dev/null +++ b/db/src/main/resources/sql/flyway/V51__use_composite_primary_key_for_domain_history_table.sql @@ -0,0 +1,34 @@ +-- Copyright 2020 The Nomulus Authors. All Rights Reserved. +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. + +alter sequence "history_id_sequence" increment 50; + +alter table "DomainHistory" alter column history_revision_id drop default; +alter table "ContactHistory" alter column history_revision_id drop default; +alter table "HostHistory" alter column history_revision_id drop default; + +alter table if exists "DomainHistoryHost" + drop constraint fk6b8eqdxwe3guc56tgpm89atx; + +alter table "DomainHistory" drop constraint "DomainHistory_pkey"; + +alter table "DomainHistory" + add constraint "DomainHistory_pkey" primary key (domain_repo_id, history_revision_id); + +alter table "DomainHistoryHost" add column domain_history_domain_repo_id text not null; + +alter table if exists "DomainHistoryHost" + add constraint FKa9woh3hu8gx5x0vly6bai327n + foreign key (domain_history_domain_repo_id, domain_history_history_revision_id) + references "DomainHistory"; 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 f8a9bc111..1b8dcc50e 100644 --- a/db/src/main/resources/sql/schema/db-schema.sql.generated +++ b/db/src/main/resources/sql/schema/db-schema.sql.generated @@ -11,7 +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 sequence history_id_sequence start 1 increment 50; create table "AllocationToken" ( token text not null, @@ -286,7 +286,8 @@ create sequence history_id_sequence start 1 increment 1; ); create table "DomainHistory" ( - history_revision_id int8 not null, + domain_repo_id text not null, + history_revision_id int8 not null, history_by_superuser boolean not null, history_registrar_id text, history_modification_time timestamptz not null, @@ -341,12 +342,12 @@ create sequence history_id_sequence start 1 increment 1; last_epp_update_time timestamptz, statuses text[], update_timestamp timestamptz, - domain_repo_id text not null, - primary key (history_revision_id) + primary key (domain_repo_id, history_revision_id) ); create table "DomainHistoryHost" ( - domain_history_history_revision_id int8 not null, + domain_history_domain_repo_id text not null, + domain_history_history_revision_id int8 not null, host_repo_id text ); @@ -644,8 +645,8 @@ create index spec11threatmatch_check_date_idx on "Spec11ThreatMatch" (check_date references "ClaimsList"; alter table if exists "DomainHistoryHost" - add constraint FK378h8v3j8qd8xtjn2e0bcmrtj - foreign key (domain_history_history_revision_id) + add constraint FKa9woh3hu8gx5x0vly6bai327n + foreign key (domain_history_domain_repo_id, domain_history_history_revision_id) references "DomainHistory"; alter table if exists "DomainHost" diff --git a/db/src/main/resources/sql/schema/nomulus.golden.sql b/db/src/main/resources/sql/schema/nomulus.golden.sql index 36cca9af3..8edfde76f 100644 --- a/db/src/main/resources/sql/schema/nomulus.golden.sql +++ b/db/src/main/resources/sql/schema/nomulus.golden.sql @@ -275,24 +275,12 @@ CREATE TABLE public."Contact" ( ); --- --- 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: ContactHistory; Type: TABLE; Schema: public; Owner: - -- CREATE TABLE public."ContactHistory" ( - history_revision_id bigint DEFAULT nextval('public.history_id_sequence'::regclass) NOT NULL, + history_revision_id bigint NOT NULL, history_by_superuser boolean NOT NULL, history_registrar_id text, history_modification_time timestamp with time zone NOT NULL, @@ -431,7 +419,7 @@ CREATE TABLE public."Domain" ( -- CREATE TABLE public."DomainHistory" ( - history_revision_id bigint DEFAULT nextval('public.history_id_sequence'::regclass) NOT NULL, + history_revision_id bigint NOT NULL, history_by_superuser boolean NOT NULL, history_registrar_id text, history_modification_time timestamp with time zone NOT NULL, @@ -496,7 +484,8 @@ CREATE TABLE public."DomainHistory" ( CREATE TABLE public."DomainHistoryHost" ( domain_history_history_revision_id bigint NOT NULL, - host_repo_id text + host_repo_id text, + domain_history_domain_repo_id text NOT NULL ); @@ -549,7 +538,7 @@ ALTER SEQUENCE public."GracePeriod_id_seq" OWNED BY public."GracePeriod".id; -- CREATE TABLE public."HostHistory" ( - history_revision_id bigint DEFAULT nextval('public.history_id_sequence'::regclass) NOT NULL, + history_revision_id bigint NOT NULL, history_by_superuser boolean NOT NULL, history_registrar_id text NOT NULL, history_modification_time timestamp with time zone NOT NULL, @@ -929,6 +918,18 @@ CREATE SEQUENCE public."Transaction_id_seq" ALTER SEQUENCE public."Transaction_id_seq" OWNED BY public."Transaction".id; +-- +-- Name: history_id_sequence; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.history_id_sequence + START WITH 1 + INCREMENT BY 50 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + -- -- Name: BillingCancellation billing_cancellation_id; Type: DEFAULT; Schema: public; Owner: - -- @@ -1083,7 +1084,7 @@ ALTER TABLE ONLY public."Cursor" -- ALTER TABLE ONLY public."DomainHistory" - ADD CONSTRAINT "DomainHistory_pkey" PRIMARY KEY (history_revision_id); + ADD CONSTRAINT "DomainHistory_pkey" PRIMARY KEY (domain_repo_id, history_revision_id); -- @@ -1612,14 +1613,6 @@ ALTER TABLE ONLY public."HostHistory" ADD CONSTRAINT fk3d09knnmxrt6iniwnp8j2ykga FOREIGN KEY (history_registrar_id) REFERENCES public."Registrar"(registrar_id); --- --- Name: DomainHistoryHost fk6b8eqdxwe3guc56tgpm89atx; Type: FK CONSTRAINT; Schema: public; Owner: - --- - -ALTER TABLE ONLY public."DomainHistoryHost" - ADD CONSTRAINT fk6b8eqdxwe3guc56tgpm89atx FOREIGN KEY (domain_history_history_revision_id) REFERENCES public."DomainHistory"(history_revision_id); - - -- -- Name: ClaimsEntry fk6sc6at5hedffc0nhdcab6ivuq; Type: FK CONSTRAINT; Schema: public; Owner: - -- @@ -1916,6 +1909,14 @@ ALTER TABLE ONLY public."PollMessage" ADD CONSTRAINT fk_poll_message_transfer_response_losing_registrar_id FOREIGN KEY (transfer_response_losing_registrar_id) REFERENCES public."Registrar"(registrar_id); +-- +-- Name: DomainHistoryHost fka9woh3hu8gx5x0vly6bai327n; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public."DomainHistoryHost" + ADD CONSTRAINT fka9woh3hu8gx5x0vly6bai327n FOREIGN KEY (domain_history_domain_repo_id, domain_history_history_revision_id) REFERENCES public."DomainHistory"(domain_repo_id, history_revision_id); + + -- -- Name: DomainHost fkfmi7bdink53swivs390m2btxg; Type: FK CONSTRAINT; Schema: public; Owner: - --