Add domain-specific history fields to DomainHistory objects (#794)

* Add domain-specific history fields to DomainHistory objects

* Add javadoc for Hibernate-only methods

* V52 -> V54

* Use only a single DomainTransactionRecord table

* Add nullables and fix up a comment

* V54 -> V55

* Regenerate db schema

* Regen SQL file
This commit is contained in:
gbrodman 2020-09-18 15:55:17 -04:00 committed by GitHub
parent 89a9190d14
commit dc6d3a4756
9 changed files with 255 additions and 19 deletions

View file

@ -23,12 +23,17 @@ import google.registry.model.EppResource;
import google.registry.model.ImmutableObject; import google.registry.model.ImmutableObject;
import google.registry.model.domain.DomainHistory.DomainHistoryId; import google.registry.model.domain.DomainHistory.DomainHistoryId;
import google.registry.model.host.HostResource; import google.registry.model.host.HostResource;
import google.registry.model.reporting.DomainTransactionRecord;
import google.registry.model.reporting.HistoryEntry; import google.registry.model.reporting.HistoryEntry;
import google.registry.persistence.VKey; import google.registry.persistence.VKey;
import java.io.Serializable; import java.io.Serializable;
import java.util.Set; import java.util.Set;
import javax.annotation.Nullable;
import javax.persistence.Access; import javax.persistence.Access;
import javax.persistence.AccessType; import javax.persistence.AccessType;
import javax.persistence.AttributeOverride;
import javax.persistence.AttributeOverrides;
import javax.persistence.CascadeType;
import javax.persistence.Column; import javax.persistence.Column;
import javax.persistence.ElementCollection; import javax.persistence.ElementCollection;
import javax.persistence.Entity; import javax.persistence.Entity;
@ -37,7 +42,9 @@ import javax.persistence.GenerationType;
import javax.persistence.Id; import javax.persistence.Id;
import javax.persistence.IdClass; import javax.persistence.IdClass;
import javax.persistence.Index; import javax.persistence.Index;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable; import javax.persistence.JoinTable;
import javax.persistence.OneToMany;
import javax.persistence.PostLoad; import javax.persistence.PostLoad;
import javax.persistence.Table; import javax.persistence.Table;
@ -75,6 +82,50 @@ public class DomainHistory extends HistoryEntry {
@Column(name = "host_repo_id") @Column(name = "host_repo_id")
Set<VKey<HostResource>> nsHosts; Set<VKey<HostResource>> nsHosts;
@Override
@Nullable
@Access(AccessType.PROPERTY)
@AttributeOverrides({
@AttributeOverride(
name = "unit",
column = @Column(name = "historyPeriodUnit")),
@AttributeOverride(
name = "value",
column = @Column(name = "historyPeriodValue"))
})
public Period getPeriod() {
return super.getPeriod();
}
/**
* For transfers, the id of the other registrar.
*
* <p>For requests and cancels, the other registrar is the losing party (because the registrar
* sending the EPP transfer command is the gaining party). For approves and rejects, the other
* registrar is the gaining party.
*/
@Nullable
@Access(AccessType.PROPERTY)
@Column(name = "historyOtherRegistrarId")
public String getOtherRegistrarId() {
return super.getOtherClientId();
}
/**
* Logging field for transaction reporting.
*
* <p>This will be empty for any DomainHistory/HistoryEntry generated before this field was added,
* mid-2017, as well as any action that does not generate billable events (e.g. updates).
*/
@Access(AccessType.PROPERTY)
@OneToMany(cascade = {CascadeType.ALL})
@JoinColumn(name = "historyRevisionId", referencedColumnName = "historyRevisionId")
@JoinColumn(name = "domainRepoId", referencedColumnName = "domainRepoId")
@Override
public Set<DomainTransactionRecord> getDomainTransactionRecords() {
return super.getDomainTransactionRecords();
}
@Id @Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "TempHistorySequenceGenerator") @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "TempHistorySequenceGenerator")
@Column(name = "historyRevisionId") @Column(name = "historyRevisionId")

View file

@ -31,9 +31,9 @@ public class Period extends ImmutableObject {
@XmlAttribute @XmlAttribute
Unit unit; Unit unit;
@XmlValue @XmlValue Integer value;
Integer value;
@Enumerated(EnumType.STRING)
public Unit getUnit() { public Unit getUnit() {
return unit; return unit;
} }
@ -42,6 +42,18 @@ public class Period extends ImmutableObject {
return value; return value;
} }
/** This method exists solely to satisfy Hibernate. Use {@link #create(int, Unit)} instead. */
@SuppressWarnings("UnusedMethod")
private void setUnit(Unit unit) {
this.unit = unit;
}
/** This method exists solely to satisfy Hibernate. Use {@link #create(int, Unit)} instead. */
@SuppressWarnings("UnusedMethod")
private void setValue(Integer value) {
this.value = value;
}
/** The unit enum. */ /** The unit enum. */
public enum Unit { public enum Unit {
@XmlEnumValue("y") @XmlEnumValue("y")

View file

@ -19,8 +19,16 @@ import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import com.googlecode.objectify.annotation.Embed; import com.googlecode.objectify.annotation.Embed;
import com.googlecode.objectify.annotation.Ignore;
import google.registry.model.Buildable; import google.registry.model.Buildable;
import google.registry.model.ImmutableObject; import google.registry.model.ImmutableObject;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import org.joda.time.DateTime; import org.joda.time.DateTime;
/** /**
@ -34,9 +42,16 @@ import org.joda.time.DateTime;
* uses HistoryEntry.otherClientId because the losing party in a transfer is always the otherClient. * uses HistoryEntry.otherClientId because the losing party in a transfer is always the otherClient.
*/ */
@Embed @Embed
@Entity
public class DomainTransactionRecord extends ImmutableObject implements Buildable { public class DomainTransactionRecord extends ImmutableObject implements Buildable {
@Id
@Ignore
@GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
/** The TLD this record operates on. */ /** The TLD this record operates on. */
@Column(nullable = false)
String tld; String tld;
/** /**
@ -50,9 +65,12 @@ public class DomainTransactionRecord extends ImmutableObject implements Buildabl
* href="https://www.icann.org/resources/unthemed-pages/registry-agmt-appc-10-2001-05-11-en"> * href="https://www.icann.org/resources/unthemed-pages/registry-agmt-appc-10-2001-05-11-en">
* Grace period spec</a> * Grace period spec</a>
*/ */
@Column(nullable = false)
DateTime reportingTime; DateTime reportingTime;
/** The transaction report field we add reportAmount to for this registrar. */ /** The transaction report field we add reportAmount to for this registrar. */
@Column(nullable = false)
@Enumerated(value = EnumType.STRING)
TransactionReportField reportField; TransactionReportField reportField;
/** /**
@ -67,6 +85,7 @@ public class DomainTransactionRecord extends ImmutableObject implements Buildabl
* original SUCCESSFUL transfer counters. Finally, if we explicitly allow a transfer, the report * original SUCCESSFUL transfer counters. Finally, if we explicitly allow a transfer, the report
* amount is 0, as we've already counted the transfer in the original request. * amount is 0, as we've already counted the transfer in the original request.
*/ */
@Column(nullable = false)
Integer reportAmount; Integer reportAmount;
/** /**

View file

@ -202,7 +202,7 @@ public class HistoryEntry extends ImmutableObject implements Buildable, Datastor
return id == null ? 0L : id; return id == null ? 0L : id;
} }
// This method is required by Hibernate. /** This method exists solely to satisfy Hibernate. Use the {@link Builder} instead. */
@SuppressWarnings("UnusedMethod") @SuppressWarnings("UnusedMethod")
private void setId(long id) { private void setId(long id) {
this.id = id; this.id = id;
@ -254,10 +254,28 @@ public class HistoryEntry extends ImmutableObject implements Buildable, Datastor
return requestedByRegistrar; return requestedByRegistrar;
} }
public ImmutableSet<DomainTransactionRecord> getDomainTransactionRecords() { public Set<DomainTransactionRecord> getDomainTransactionRecords() {
return nullToEmptyImmutableCopy(domainTransactionRecords); return nullToEmptyImmutableCopy(domainTransactionRecords);
} }
/** This method exists solely to satisfy Hibernate. Use the {@link Builder} instead. */
@SuppressWarnings("UnusedMethod")
private void setPeriod(Period period) {
this.period = period;
}
/** This method exists solely to satisfy Hibernate. Use the {@link Builder} instead. */
@SuppressWarnings("UnusedMethod")
private void setOtherRegistrarId(String otherRegistrarId) {
this.otherClientId = otherRegistrarId;
}
/** This method exists solely to satisfy Hibernate. Use the {@link Builder} instead. */
@SuppressWarnings("UnusedMethod")
private void setDomainTransactionRecords(Set<DomainTransactionRecord> domainTransactionRecords) {
this.domainTransactionRecords = ImmutableSet.copyOf(domainTransactionRecords);
}
public static VKey<HistoryEntry> createVKey(Key<HistoryEntry> key) { public static VKey<HistoryEntry> createVKey(Key<HistoryEntry> key) {
// TODO(b/159207551): This will likely need some revision. As it stands, this method was // TODO(b/159207551): This will likely need some revision. As it stands, this method was
// introduced purely to facilitate testing of VKey specialization in VKeyTranslatorFactory. // introduced purely to facilitate testing of VKey specialization in VKeyTranslatorFactory.

View file

@ -45,26 +45,27 @@
<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.domain.DomainHistory</class> <class>google.registry.model.domain.DomainHistory</class>
<class>google.registry.model.domain.GracePeriod</class>
<class>google.registry.model.domain.secdns.DelegationSignerData</class>
<class>google.registry.model.domain.token.AllocationToken</class> <class>google.registry.model.domain.token.AllocationToken</class>
<class>google.registry.model.host.HostHistory</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.RegistrarContact</class>
<class>google.registry.model.registry.label.PremiumList</class>
<class>google.registry.model.registry.Registry</class>
<class>google.registry.model.reporting.Spec11ThreatMatch</class>
<class>google.registry.persistence.transaction.TransactionEntity</class>
<class>google.registry.model.tmch.ClaimsListShard</class>
<class>google.registry.schema.domain.RegistryLock</class>
<class>google.registry.schema.cursor.Cursor</class>
<class>google.registry.schema.server.Lock</class>
<class>google.registry.schema.tld.PremiumEntry</class>
<class>google.registry.model.domain.secdns.DelegationSignerData</class>
<class>google.registry.model.domain.GracePeriod</class>
<class>google.registry.model.poll.PollMessage</class> <class>google.registry.model.poll.PollMessage</class>
<class>google.registry.model.poll.PollMessage$OneTime</class> <class>google.registry.model.poll.PollMessage$OneTime</class>
<class>google.registry.model.poll.PollMessage$Autorenew</class> <class>google.registry.model.poll.PollMessage$Autorenew</class>
<class>google.registry.model.registrar.Registrar</class>
<class>google.registry.model.registrar.RegistrarContact</class>
<class>google.registry.model.registry.label.PremiumList</class>
<class>google.registry.model.registry.label.ReservedList</class> <class>google.registry.model.registry.label.ReservedList</class>
<class>google.registry.model.registry.Registry</class>
<class>google.registry.model.reporting.DomainTransactionRecord</class>
<class>google.registry.model.reporting.Spec11ThreatMatch</class>
<class>google.registry.model.tmch.ClaimsListShard</class>
<class>google.registry.persistence.transaction.TransactionEntity</class>
<class>google.registry.schema.cursor.Cursor</class>
<class>google.registry.schema.domain.RegistryLock</class>
<class>google.registry.schema.server.Lock</class>
<class>google.registry.schema.tld.PremiumEntry</class>
<!-- Customized type converters --> <!-- Customized type converters -->
<class>google.registry.persistence.converter.AllocationTokenStatusTransitionConverter</class> <class>google.registry.persistence.converter.AllocationTokenStatusTransitionConverter</class>

View file

@ -17,6 +17,7 @@ package google.registry.model.history;
import static com.google.common.collect.ImmutableSet.toImmutableSet; import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.ImmutableObjectSubject.assertAboutImmutableObjects; import static google.registry.model.ImmutableObjectSubject.assertAboutImmutableObjects;
import static google.registry.model.ImmutableObjectSubject.immutableObjectCorrespondence;
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm; import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm; import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.testing.DatastoreHelper.newContactResourceWithRoid; import static google.registry.testing.DatastoreHelper.newContactResourceWithRoid;
@ -25,14 +26,18 @@ import static google.registry.testing.DatastoreHelper.newHostResourceWithRoid;
import static google.registry.testing.SqlHelper.saveRegistrar; import static google.registry.testing.SqlHelper.saveRegistrar;
import static java.nio.charset.StandardCharsets.UTF_8; import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.collect.ImmutableSet;
import com.googlecode.objectify.Key; import com.googlecode.objectify.Key;
import google.registry.model.EntityTestCase; import google.registry.model.EntityTestCase;
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.domain.DomainContent; import google.registry.model.domain.DomainContent;
import google.registry.model.domain.DomainHistory; import google.registry.model.domain.DomainHistory;
import google.registry.model.domain.Period;
import google.registry.model.eppcommon.Trid; import google.registry.model.eppcommon.Trid;
import google.registry.model.host.HostResource; import google.registry.model.host.HostResource;
import google.registry.model.reporting.DomainTransactionRecord;
import google.registry.model.reporting.DomainTransactionRecord.TransactionReportField;
import google.registry.model.reporting.HistoryEntry; import google.registry.model.reporting.HistoryEntry;
import google.registry.persistence.VKey; import google.registry.persistence.VKey;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -125,10 +130,24 @@ public class DomainHistoryTest extends EntityTestCase {
static void assertDomainHistoriesEqual(DomainHistory one, DomainHistory two) { static void assertDomainHistoriesEqual(DomainHistory one, DomainHistory two) {
assertAboutImmutableObjects() assertAboutImmutableObjects()
.that(one) .that(one)
.isEqualExceptFields(two, "domainContent", "domainRepoId", "parent", "nsHosts"); .isEqualExceptFields(
two, "domainContent", "domainRepoId", "parent", "nsHosts", "domainTransactionRecords");
// NB: the record's ID gets reset by Hibernate, causing the hash code to differ so we have to
// compare it separately
assertThat(one.getDomainTransactionRecords())
.comparingElementsUsing(immutableObjectCorrespondence())
.containsExactlyElementsIn(two.getDomainTransactionRecords());
} }
private DomainHistory createDomainHistory(DomainContent domain) { private DomainHistory createDomainHistory(DomainContent domain) {
DomainTransactionRecord transactionRecord =
new DomainTransactionRecord.Builder()
.setTld("tld")
.setReportingTime(fakeClock.nowUtc())
.setReportField(TransactionReportField.NET_ADDS_1_YR)
.setReportAmount(1)
.build();
return new DomainHistory.Builder() return new DomainHistory.Builder()
.setType(HistoryEntry.Type.DOMAIN_CREATE) .setType(HistoryEntry.Type.DOMAIN_CREATE)
.setXmlBytes("<xml></xml>".getBytes(UTF_8)) .setXmlBytes("<xml></xml>".getBytes(UTF_8))
@ -140,6 +159,9 @@ public class DomainHistoryTest extends EntityTestCase {
.setRequestedByRegistrar(true) .setRequestedByRegistrar(true)
.setDomainContent(domain) .setDomainContent(domain)
.setDomainRepoId(domain.getRepoId()) .setDomainRepoId(domain.getRepoId())
.setDomainTransactionRecords(ImmutableSet.of(transactionRecord))
.setOtherClientId("otherClient")
.setPeriod(Period.create(1, Period.Unit.YEARS))
.build(); .build();
} }
} }

View file

@ -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 TABLE "DomainHistory" ADD COLUMN history_other_registrar_id text;
ALTER TABLE "DomainHistory" ADD COLUMN history_period_unit text;
ALTER TABLE "DomainHistory" ADD COLUMN history_period_value int4;
CREATE TABLE "DomainTransactionRecord" (
id bigserial NOT NULL,
report_amount int4 NOT NULL,
report_field text NOT NULL,
reporting_time timestamptz NOT NULL,
tld text NOT NULL,
domain_repo_id text,
history_revision_id int8,
PRIMARY KEY (id)
);
ALTER TABLE IF EXISTS "DomainTransactionRecord"
ADD CONSTRAINT FKcjqe54u72kha71vkibvxhjye7
FOREIGN KEY (domain_repo_id, history_revision_id)
REFERENCES "DomainHistory";

View file

@ -342,6 +342,9 @@ create sequence temp_history_id_sequence start 1 increment 50;
last_epp_update_time timestamptz, last_epp_update_time timestamptz,
statuses text[], statuses text[],
update_timestamp timestamptz, update_timestamp timestamptz,
history_other_registrar_id text,
history_period_unit text,
history_period_value int4,
primary key (domain_repo_id, history_revision_id) primary key (domain_repo_id, history_revision_id)
); );
@ -356,6 +359,17 @@ create sequence temp_history_id_sequence start 1 increment 50;
host_repo_id text host_repo_id text
); );
create table "DomainTransactionRecord" (
id bigserial not null,
report_amount int4 not null,
report_field text not null,
reporting_time timestamptz not null,
tld text not null,
domain_repo_id text,
history_revision_id int8,
primary key (id)
);
create table "GracePeriod" ( create table "GracePeriod" (
id bigserial not null, id bigserial not null,
billing_event_id int8, billing_event_id int8,
@ -696,6 +710,11 @@ create index spec11threatmatch_check_date_idx on "Spec11ThreatMatch" (check_date
foreign key (domain_repo_id) foreign key (domain_repo_id)
references "Domain"; references "Domain";
alter table if exists "DomainTransactionRecord"
add constraint FKcjqe54u72kha71vkibvxhjye7
foreign key (domain_repo_id, history_revision_id)
references "DomainHistory";
alter table if exists "GracePeriod" alter table if exists "GracePeriod"
add constraint FK2mys4hojm6ev2g9tmy5aq6m7g add constraint FK2mys4hojm6ev2g9tmy5aq6m7g
foreign key (domain_repo_id) foreign key (domain_repo_id)

View file

@ -474,7 +474,10 @@ CREATE TABLE public."DomainHistory" (
statuses text[], statuses text[],
update_timestamp timestamp with time zone, update_timestamp timestamp with time zone,
domain_repo_id text NOT NULL, domain_repo_id text NOT NULL,
autorenew_end_time timestamp with time zone autorenew_end_time timestamp with time zone,
history_other_registrar_id text,
history_period_unit text,
history_period_value integer
); );
@ -499,6 +502,40 @@ CREATE TABLE public."DomainHost" (
); );
--
-- Name: DomainTransactionRecord; Type: TABLE; Schema: public; Owner: -
--
CREATE TABLE public."DomainTransactionRecord" (
id bigint NOT NULL,
report_amount integer NOT NULL,
report_field text NOT NULL,
reporting_time timestamp with time zone NOT NULL,
tld text NOT NULL,
domain_repo_id text,
history_revision_id bigint
);
--
-- Name: DomainTransactionRecord_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--
CREATE SEQUENCE public."DomainTransactionRecord_id_seq"
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
--
-- Name: DomainTransactionRecord_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--
ALTER SEQUENCE public."DomainTransactionRecord_id_seq" OWNED BY public."DomainTransactionRecord".id;
-- --
-- Name: GracePeriod; Type: TABLE; Schema: public; Owner: - -- Name: GracePeriod; Type: TABLE; Schema: public; Owner: -
-- --
@ -1016,6 +1053,13 @@ ALTER TABLE ONLY public."BillingRecurrence" ALTER COLUMN billing_recurrence_id S
ALTER TABLE ONLY public."ClaimsList" ALTER COLUMN revision_id SET DEFAULT nextval('public."ClaimsList_revision_id_seq"'::regclass); ALTER TABLE ONLY public."ClaimsList" ALTER COLUMN revision_id SET DEFAULT nextval('public."ClaimsList_revision_id_seq"'::regclass);
--
-- Name: DomainTransactionRecord id; Type: DEFAULT; Schema: public; Owner: -
--
ALTER TABLE ONLY public."DomainTransactionRecord" ALTER COLUMN id SET DEFAULT nextval('public."DomainTransactionRecord_id_seq"'::regclass);
-- --
-- Name: GracePeriod id; Type: DEFAULT; Schema: public; Owner: - -- Name: GracePeriod id; Type: DEFAULT; Schema: public; Owner: -
-- --
@ -1145,6 +1189,14 @@ ALTER TABLE ONLY public."DomainHistory"
ADD CONSTRAINT "DomainHistory_pkey" PRIMARY KEY (domain_repo_id, history_revision_id); ADD CONSTRAINT "DomainHistory_pkey" PRIMARY KEY (domain_repo_id, history_revision_id);
--
-- Name: DomainTransactionRecord DomainTransactionRecord_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public."DomainTransactionRecord"
ADD CONSTRAINT "DomainTransactionRecord_pkey" PRIMARY KEY (id);
-- --
-- Name: Domain Domain_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- Name: Domain Domain_pkey; Type: CONSTRAINT; Schema: public; Owner: -
-- --
@ -1983,6 +2035,14 @@ 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); 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: DomainTransactionRecord fkcjqe54u72kha71vkibvxhjye7; Type: FK CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public."DomainTransactionRecord"
ADD CONSTRAINT fkcjqe54u72kha71vkibvxhjye7 FOREIGN KEY (domain_repo_id, history_revision_id) REFERENCES public."DomainHistory"(domain_repo_id, history_revision_id);
-- --
-- Name: DomainHost fkfmi7bdink53swivs390m2btxg; Type: FK CONSTRAINT; Schema: public; Owner: - -- Name: DomainHost fkfmi7bdink53swivs390m2btxg; Type: FK CONSTRAINT; Schema: public; Owner: -
-- --