diff --git a/core/src/main/java/google/registry/model/domain/DomainBase.java b/core/src/main/java/google/registry/model/domain/DomainBase.java index 285c311f9..5ac77edc5 100644 --- a/core/src/main/java/google/registry/model/domain/DomainBase.java +++ b/core/src/main/java/google/registry/model/domain/DomainBase.java @@ -35,6 +35,7 @@ import javax.persistence.Index; import javax.persistence.JoinColumn; import javax.persistence.JoinTable; import javax.persistence.OneToMany; +import javax.persistence.PostLoad; import javax.persistence.Table; import org.joda.time.DateTime; @@ -106,6 +107,12 @@ public class DomainBase extends DomainContent return gracePeriods; } + @PostLoad + @SuppressWarnings("UnusedMethod") + private final void postLoad() { + restoreOfyKeys(getRepoId()); + } + @Override public VKey createVKey() { return VKey.create(DomainBase.class, getRepoId(), Key.create(this)); diff --git a/core/src/main/java/google/registry/model/domain/DomainContent.java b/core/src/main/java/google/registry/model/domain/DomainContent.java index 32ecd0071..bc73a9ff8 100644 --- a/core/src/main/java/google/registry/model/domain/DomainContent.java +++ b/core/src/main/java/google/registry/model/domain/DomainContent.java @@ -40,6 +40,7 @@ import com.google.common.collect.ImmutableSortedSet; import com.google.common.collect.Ordering; import com.google.common.collect.Sets; import com.google.common.collect.Streams; +import com.googlecode.objectify.Key; import com.googlecode.objectify.annotation.Ignore; import com.googlecode.objectify.annotation.IgnoreSave; import com.googlecode.objectify.annotation.Index; @@ -49,6 +50,7 @@ import google.registry.flows.ResourceFlowUtils; import google.registry.model.EppResource; import google.registry.model.EppResource.ResourceWithTransferData; import google.registry.model.billing.BillingEvent; +import google.registry.model.common.EntityGroupRoot; import google.registry.model.contact.ContactResource; import google.registry.model.domain.launch.LaunchNotice; import google.registry.model.domain.rgp.GracePeriodStatus; @@ -57,6 +59,7 @@ import google.registry.model.eppcommon.StatusValue; import google.registry.model.host.HostResource; import google.registry.model.poll.PollMessage; import google.registry.model.registry.Registry; +import google.registry.model.reporting.HistoryEntry; import google.registry.model.transfer.DomainTransferData; import google.registry.model.transfer.TransferStatus; import google.registry.persistence.VKey; @@ -209,6 +212,15 @@ public class DomainContent extends EppResource @Column(name = "deletion_poll_message_id") VKey deletePollMessage; + /** + * History record for the delete poll message. + * + *

Here so we can restore the original ofy key from sql. + */ + @Column(name = "deletion_poll_message_history_id") + @Ignore + Long deletePollMessageHistoryId; + /** * The recurring billing event associated with this domain's autorenewals. * @@ -220,6 +232,15 @@ public class DomainContent extends EppResource @Column(name = "billing_recurrence_id") VKey autorenewBillingEvent; + /** + * History record for the autorenew billing event. + * + *

Here so we can restore the original ofy key from sql. + */ + @Column(name = "billing_recurrence_history_id") + @Ignore + Long autorenewBillingEventHistoryId; + /** * The recurring poll message associated with this domain's autorenewals. * @@ -231,6 +252,13 @@ public class DomainContent extends EppResource @Column(name = "autorenew_poll_message_id") VKey autorenewPollMessage; + /** + * History record for the autorenew poll message. + * + *

Here so we can restore the original ofy key from sql. + */ + @Ignore Long autorenewPollMessageHistoryId; + /** The unexpired grace periods for this domain (some of which may not be active yet). */ @Transient Set gracePeriods; @@ -284,10 +312,22 @@ public class DomainContent extends EppResource nullToEmptyImmutableCopy(gracePeriods).stream() .map(gracePeriod -> gracePeriod.cloneWithDomainRepoId(getRepoId())) .collect(toImmutableSet()); + + // Restore history record ids. + autorenewPollMessageHistoryId = getHistoryId(autorenewPollMessage); + autorenewBillingEventHistoryId = getHistoryId(autorenewBillingEvent); + deletePollMessageHistoryId = getHistoryId(deletePollMessage); } + /** + * The {@link javax.persistence.PostLoad} method for {@link DomainContent}. + * + *

We name this domainContentPostLoad to distinguish it from the {@link PostLoad} method in + * DomainBase (if they share the same name, this one is never called). + */ @PostLoad - void postLoad() { + @SuppressWarnings("UnusedMethod") + private final void domainContentPostLoad() { // Reconstitute the contact list. ImmutableSet.Builder contactsBuilder = new ImmutableSet.Builder<>(); @@ -308,6 +348,48 @@ public class DomainContent extends EppResource allContacts = contactsBuilder.build(); } + /** + * Restores the composite ofy keys from SQL data. + * + *

MUST ONLY BE CALLED FROM A PostLoad method. This is a package-visible method that + * effectively mutates an immutable object. + * + *

We have to do this because: + * + *

+ */ + void restoreOfyKeys(String repoId) { + // Reconstitute the ofy keys. + Key myKey = Key.create(DomainBase.class, repoId); + deletePollMessage = restoreOfyFrom(myKey, deletePollMessage, deletePollMessageHistoryId); + autorenewBillingEvent = + restoreOfyFrom(myKey, autorenewBillingEvent, autorenewBillingEventHistoryId); + autorenewPollMessage = + restoreOfyFrom(myKey, autorenewPollMessage, autorenewPollMessageHistoryId); + } + + private VKey restoreOfyFrom(Key domainKey, VKey key, Long historyId) { + if (historyId == null) { + // This is a legacy key (or a null key, in which case this works too) + return VKey.restoreOfyFrom(key, EntityGroupRoot.class, "per-tld"); + } else { + return VKey.restoreOfyFrom(key, domainKey, HistoryEntry.class, historyId); + } + } + public ImmutableSet getSubordinateHosts() { return nullToEmptyImmutableCopy(subordinateHosts); } @@ -655,6 +737,17 @@ public class DomainContent extends EppResource + " use DomainBase instead"); } + private static Long getHistoryId(VKey key) { + if (key == null) { + return null; + } + Key parent = key.getOfyKey().getParent(); + if (parent == null || parent.getKind().equals("EntityGroupRoot")) { + return null; + } + return parent.getId(); + } + /** Predicate to determine if a given {@link DesignatedContact} is the registrant. */ static final Predicate IS_REGISTRANT = (DesignatedContact contact) -> DesignatedContact.Type.REGISTRANT.equals(contact.type); @@ -824,16 +917,19 @@ public class DomainContent extends EppResource public B setDeletePollMessage(VKey deletePollMessage) { getInstance().deletePollMessage = deletePollMessage; + getInstance().deletePollMessageHistoryId = getHistoryId(deletePollMessage); return thisCastToDerived(); } public B setAutorenewBillingEvent(VKey autorenewBillingEvent) { getInstance().autorenewBillingEvent = autorenewBillingEvent; + getInstance().autorenewBillingEventHistoryId = getHistoryId(autorenewBillingEvent); return thisCastToDerived(); } public B setAutorenewPollMessage(VKey autorenewPollMessage) { getInstance().autorenewPollMessage = autorenewPollMessage; + getInstance().autorenewPollMessageHistoryId = getHistoryId(autorenewPollMessage); return thisCastToDerived(); } 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 677ff005c..0c1cfc1fd 100644 --- a/core/src/main/java/google/registry/model/domain/DomainHistory.java +++ b/core/src/main/java/google/registry/model/domain/DomainHistory.java @@ -170,6 +170,8 @@ public class DomainHistory extends HistoryEntry implements SqlEntity { // domainContent with a null object. Unfortunately, the updateTimestamp is never null in SQL. if (domainContent.getDomainName() == null) { domainContent = null; + } else { + domainContent.restoreOfyKeys(domainRepoId); } } parent = Key.create(DomainBase.class, domainRepoId); diff --git a/core/src/test/java/google/registry/beam/initsql/InitSqlPipelineTest.java b/core/src/test/java/google/registry/beam/initsql/InitSqlPipelineTest.java index 56aadd261..0c8c4ae43 100644 --- a/core/src/test/java/google/registry/beam/initsql/InitSqlPipelineTest.java +++ b/core/src/test/java/google/registry/beam/initsql/InitSqlPipelineTest.java @@ -273,8 +273,11 @@ class InitSqlPipelineTest { "revisions", "updateTimestamp", "autorenewBillingEvent", + "autorenewBillingEventHistoryId", "autorenewPollMessage", + "autorenewPollMessageHistoryId", "deletePollMessage", + "deletePollMessageHistoryId", "nsHosts", "transferData"); assertThat(actual.getAdminContact().getSqlKey()) diff --git a/core/src/test/java/google/registry/model/domain/DomainBaseSqlTest.java b/core/src/test/java/google/registry/model/domain/DomainBaseSqlTest.java index d893add3b..08295aee1 100644 --- a/core/src/test/java/google/registry/model/domain/DomainBaseSqlTest.java +++ b/core/src/test/java/google/registry/model/domain/DomainBaseSqlTest.java @@ -25,6 +25,11 @@ import static org.joda.time.DateTimeZone.UTC; import static org.junit.jupiter.api.Assertions.fail; import com.google.common.collect.ImmutableSet; +import com.googlecode.objectify.Key; +import google.registry.model.billing.BillingEvent; +import google.registry.model.billing.BillingEvent.Flag; +import google.registry.model.billing.BillingEvent.Reason; +import google.registry.model.common.EntityGroupRoot; import google.registry.model.contact.ContactResource; import google.registry.model.domain.DesignatedContact.Type; import google.registry.model.domain.launch.LaunchNotice; @@ -33,6 +38,8 @@ import google.registry.model.domain.secdns.DelegationSignerData; import google.registry.model.eppcommon.AuthInfo.PasswordAuth; import google.registry.model.eppcommon.StatusValue; import google.registry.model.host.HostResource; +import google.registry.model.poll.PollMessage; +import google.registry.model.reporting.HistoryEntry; import google.registry.model.transfer.ContactTransferData; import google.registry.persistence.VKey; import google.registry.persistence.transaction.JpaTestRules; @@ -60,6 +67,7 @@ public class DomainBaseSqlTest { new JpaTestRules.Builder().withClock(fakeClock).buildIntegrationWithCoverageExtension(); private DomainBase domain; + private DomainHistory historyEntry; private VKey contactKey; private VKey contact2Key; private VKey host1VKey; @@ -395,6 +403,174 @@ public class DomainBaseSqlTest { }); } + @Test + void persistDomainWithCompositeVKeys() { + jpaTm() + .transact( + () -> { + historyEntry = + new DomainHistory.Builder() + .setType(HistoryEntry.Type.DOMAIN_CREATE) + .setPeriod(Period.create(1, Period.Unit.YEARS)) + .setModificationTime(DateTime.now(UTC)) + .setParent(Key.create(DomainBase.class, "4-COM")) + .setDomainRepoId("4-COM") + + // These are non-null, but I don't think some tests set them. + .setReason("felt like it") + .setRequestedByRegistrar(false) + .setXmlBytes(new byte[0]) + .build(); + BillingEvent.Recurring billEvent = + new BillingEvent.Recurring.Builder() + .setReason(Reason.RENEW) + .setFlags(ImmutableSet.of(Flag.AUTO_RENEW)) + .setTargetId("example.com") + .setClientId("registrar1") + .setDomainRepoId("4-COM") + .setDomainHistoryRevisionId(1L) + .setEventTime(DateTime.now(UTC).plusYears(1)) + .setRecurrenceEndTime(END_OF_TIME) + .setParent(historyEntry) + .build(); + PollMessage.Autorenew autorenewPollMessage = + new PollMessage.Autorenew.Builder() + .setClientId("registrar1") + .setEventTime(DateTime.now(UTC).plusYears(1)) + .setParent(historyEntry) + .build(); + PollMessage.OneTime deletePollMessage = + new PollMessage.OneTime.Builder() + .setClientId("registrar1") + .setEventTime(DateTime.now(UTC).plusYears(1)) + .setParent(historyEntry) + .build(); + + jpaTm().insert(contact); + jpaTm().insert(contact2); + jpaTm().insert(host); + domain = + domain + .asBuilder() + .setAutorenewBillingEvent(billEvent.createVKey()) + .setAutorenewPollMessage(autorenewPollMessage.createVKey()) + .setDeletePollMessage(deletePollMessage.createVKey()) + .build(); + historyEntry = historyEntry.asBuilder().setDomainContent(domain).build(); + jpaTm().insert(historyEntry); + jpaTm().insert(autorenewPollMessage); + jpaTm().insert(billEvent); + jpaTm().insert(deletePollMessage); + jpaTm().insert(domain); + }); + + // Store the existing BillingRecurrence VKey. This happens after the event has been persisted. + DomainBase persisted = jpaTm().transact(() -> jpaTm().load(domain.createVKey())); + + // Verify that the domain data has been persisted. + // dsData still isn't persisted. gracePeriods appears to have the same values but for some + // reason is showing up as different. + assertEqualDomainExcept(persisted, "creationTime", "dsData", "gracePeriods"); + + // Verify that the DomainContent object from the history record sets the fields correctly. + DomainHistory persistedHistoryEntry = + jpaTm().transact(() -> jpaTm().load(historyEntry.createVKey())); + assertThat(persistedHistoryEntry.getDomainContent().get().getAutorenewPollMessage()) + .isEqualTo(domain.getAutorenewPollMessage()); + assertThat(persistedHistoryEntry.getDomainContent().get().getAutorenewBillingEvent()) + .isEqualTo(domain.getAutorenewBillingEvent()); + assertThat(persistedHistoryEntry.getDomainContent().get().getDeletePollMessage()) + .isEqualTo(domain.getDeletePollMessage()); + } + + @Test + void persistDomainWithLegacyVKeys() { + jpaTm() + .transact( + () -> { + historyEntry = + new DomainHistory.Builder() + .setType(HistoryEntry.Type.DOMAIN_CREATE) + .setPeriod(Period.create(1, Period.Unit.YEARS)) + .setModificationTime(DateTime.now(UTC)) + .setParent(Key.create(DomainBase.class, "4-COM")) + .setDomainRepoId("4-COM") + + // These are non-null, but I don't think some tests set them. + .setReason("felt like it") + .setRequestedByRegistrar(false) + .setXmlBytes(new byte[0]) + .build(); + BillingEvent.Recurring billEvent = + new BillingEvent.Recurring.Builder() + .setReason(Reason.RENEW) + .setFlags(ImmutableSet.of(Flag.AUTO_RENEW)) + .setTargetId("example.com") + .setClientId("registrar1") + .setDomainRepoId("4-COM") + .setDomainHistoryRevisionId(1L) + .setEventTime(DateTime.now(UTC).plusYears(1)) + .setRecurrenceEndTime(END_OF_TIME) + .setParent(historyEntry) + .build(); + PollMessage.Autorenew autorenewPollMessage = + new PollMessage.Autorenew.Builder() + .setClientId("registrar1") + .setEventTime(DateTime.now(UTC).plusYears(1)) + .setParent(historyEntry) + .build(); + PollMessage.OneTime deletePollMessage = + new PollMessage.OneTime.Builder() + .setClientId("registrar1") + .setEventTime(DateTime.now(UTC).plusYears(1)) + .setParent(historyEntry) + .build(); + + jpaTm().insert(contact); + jpaTm().insert(contact2); + jpaTm().insert(host); + domain = + domain + .asBuilder() + .setAutorenewBillingEvent( + createLegacyVKey(BillingEvent.Recurring.class, billEvent.getId())) + .setAutorenewPollMessage( + createLegacyVKey(PollMessage.Autorenew.class, deletePollMessage.getId())) + .setDeletePollMessage( + createLegacyVKey(PollMessage.OneTime.class, autorenewPollMessage.getId())) + .build(); + historyEntry = historyEntry.asBuilder().setDomainContent(domain).build(); + jpaTm().insert(historyEntry); + jpaTm().insert(autorenewPollMessage); + jpaTm().insert(billEvent); + jpaTm().insert(deletePollMessage); + jpaTm().insert(domain); + }); + + // Store the existing BillingRecurrence VKey. This happens after the event has been persisted. + DomainBase persisted = jpaTm().transact(() -> jpaTm().load(domain.createVKey())); + + // Verify that the domain data has been persisted. + // dsData still isn't persisted. gracePeriods appears to have the same values but for some + // reason is showing up as different. + assertEqualDomainExcept(persisted, "creationTime", "dsData", "gracePeriods"); + + // Verify that the DomainContent object from the history record sets the fields correctly. + DomainHistory persistedHistoryEntry = + jpaTm().transact(() -> jpaTm().load(historyEntry.createVKey())); + assertThat(persistedHistoryEntry.getDomainContent().get().getAutorenewPollMessage()) + .isEqualTo(domain.getAutorenewPollMessage()); + assertThat(persistedHistoryEntry.getDomainContent().get().getAutorenewBillingEvent()) + .isEqualTo(domain.getAutorenewBillingEvent()); + assertThat(persistedHistoryEntry.getDomainContent().get().getDeletePollMessage()) + .isEqualTo(domain.getDeletePollMessage()); + } + + private VKey createLegacyVKey(Class clazz, long id) { + return VKey.create( + clazz, id, Key.create(Key.create(EntityGroupRoot.class, "per-tld"), clazz, id)); + } + private void assertEqualDomainExcept(DomainBase thatDomain, String... excepts) { // Fix DS data, since we can't persist it yet. thatDomain = diff --git a/core/src/test/java/google/registry/model/poll/PollMessageTest.java b/core/src/test/java/google/registry/model/poll/PollMessageTest.java index deb68b9a8..6c8ca679b 100644 --- a/core/src/test/java/google/registry/model/poll/PollMessageTest.java +++ b/core/src/test/java/google/registry/model/poll/PollMessageTest.java @@ -67,6 +67,7 @@ public class PollMessageTest extends EntityTestCase { .build()); oneTime = new PollMessage.OneTime.Builder() + .setId(100L) .setClientId("TheRegistrar") .setEventTime(fakeClock.nowUtc()) .setMsg("Test poll message") @@ -74,6 +75,7 @@ public class PollMessageTest extends EntityTestCase { .build(); autoRenew = new PollMessage.Autorenew.Builder() + .setId(200L) .setClientId("TheRegistrar") .setEventTime(fakeClock.nowUtc()) .setMsg("Test poll message") diff --git a/db/src/main/resources/sql/flyway.txt b/db/src/main/resources/sql/flyway.txt index 665caf95e..2b2aa1098 100644 --- a/db/src/main/resources/sql/flyway.txt +++ b/db/src/main/resources/sql/flyway.txt @@ -58,3 +58,4 @@ V57__history_null_content.sql V58__drop_default_value_and_sequences_for_billing_event.sql V59__use_composite_primary_key_for_contact_and_host_history_table.sql V60__remove_pollmessage_sequence.sql +V61__domain_hist_columns.sql diff --git a/db/src/main/resources/sql/flyway/V61__domain_hist_columns.sql b/db/src/main/resources/sql/flyway/V61__domain_hist_columns.sql new file mode 100644 index 000000000..f2f0e2c20 --- /dev/null +++ b/db/src/main/resources/sql/flyway/V61__domain_hist_columns.sql @@ -0,0 +1,39 @@ +-- 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. + +-- These history ids are technically foreign keys, we don't want to constrain +-- them because they're temporary and we don't need to because they are +-- already indirectly constrained by the relationships with the +-- PollMessages/BillingEvents involved. +ALTER TABLE "Domain" ADD COLUMN billing_recurrence_history_id int8; +ALTER TABLE "Domain" ADD COLUMN autorenew_poll_message_history_id int8; +ALTER TABLE "Domain" ADD COLUMN deletion_poll_message_history_id int8; +ALTER TABLE "DomainHistory" ADD COLUMN billing_recurrence_history_id int8; +ALTER TABLE "DomainHistory" ADD COLUMN autorenew_poll_message_history_id int8; +ALTER TABLE "DomainHistory" ADD COLUMN deletion_poll_message_history_id int8; + +-- Drop and re-add DomainHistory's Domain FK constraint to make it deferrable, +-- this breaks a cycle with Domain -> PollMessage|BillingEvent -> History. +ALTER TABLE "DomainHistory" DROP CONSTRAINT fk_domain_history_domain_repo_id; +ALTER TABLE "DomainHistory" + ADD CONSTRAINT fk_domain_history_domain_repo_id + FOREIGN KEY (domain_repo_id) REFERENCES "Domain"(repo_id) + DEFERRABLE INITIALLY DEFERRED; + +-- Same for PollMessage -> Domain, breaking Domain -> PollMessage -> Domain. +ALTER TABLE "PollMessage" DROP CONSTRAINT fk_poll_message_domain_repo_id; +ALTER TABLE "PollMessage" + ADD CONSTRAINT fk_poll_message_domain_repo_id + FOREIGN KEY (domain_repo_id) REFERENCES "Domain"(repo_id) + DEFERRABLE INITIALLY DEFERRED; 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 36d0c5866..e629bc448 100644 --- a/db/src/main/resources/sql/schema/db-schema.sql.generated +++ b/db/src/main/resources/sql/schema/db-schema.sql.generated @@ -249,10 +249,13 @@ create sequence temp_history_id_sequence start 1 increment 50; auth_info_repo_id text, auth_info_value text, billing_recurrence_id int8, + billing_recurrence_history_id int8, autorenew_end_time timestamptz, autorenew_poll_message_id int8, + autorenew_poll_message_history_id int8, billing_contact text, deletion_poll_message_id int8, + deletion_poll_message_history_id int8, domain_name text, idn_table_name text, last_transfer_time timestamptz, @@ -301,10 +304,13 @@ create sequence temp_history_id_sequence start 1 increment 50; auth_info_repo_id text, auth_info_value text, billing_recurrence_id int8, + billing_recurrence_history_id int8, autorenew_end_time timestamptz, autorenew_poll_message_id int8, + autorenew_poll_message_history_id int8, billing_contact text, deletion_poll_message_id int8, + deletion_poll_message_history_id int8, domain_name text, idn_table_name text, last_transfer_time timestamptz, diff --git a/db/src/main/resources/sql/schema/nomulus.golden.sql b/db/src/main/resources/sql/schema/nomulus.golden.sql index 0fc6e5166..aa4ce1bb7 100644 --- a/db/src/main/resources/sql/schema/nomulus.golden.sql +++ b/db/src/main/resources/sql/schema/nomulus.golden.sql @@ -353,7 +353,10 @@ CREATE TABLE public."Domain" ( billing_recurrence_id bigint, autorenew_poll_message_id bigint, deletion_poll_message_id bigint, - autorenew_end_time timestamp with time zone + autorenew_end_time timestamp with time zone, + billing_recurrence_history_id bigint, + autorenew_poll_message_history_id bigint, + deletion_poll_message_history_id bigint ); @@ -420,7 +423,10 @@ CREATE TABLE public."DomainHistory" ( autorenew_end_time timestamp with time zone, history_other_registrar_id text, history_period_unit text, - history_period_value integer + history_period_value integer, + billing_recurrence_history_id bigint, + autorenew_poll_message_history_id bigint, + deletion_poll_message_history_id bigint ); @@ -1768,7 +1774,7 @@ ALTER TABLE ONLY public."Domain" -- ALTER TABLE ONLY public."DomainHistory" - ADD CONSTRAINT fk_domain_history_domain_repo_id FOREIGN KEY (domain_repo_id) REFERENCES public."Domain"(repo_id); + ADD CONSTRAINT fk_domain_history_domain_repo_id FOREIGN KEY (domain_repo_id) REFERENCES public."Domain"(repo_id) DEFERRABLE INITIALLY DEFERRED; -- @@ -1904,7 +1910,7 @@ ALTER TABLE ONLY public."PollMessage" -- ALTER TABLE ONLY public."PollMessage" - ADD CONSTRAINT fk_poll_message_domain_repo_id FOREIGN KEY (domain_repo_id) REFERENCES public."Domain"(repo_id); + ADD CONSTRAINT fk_poll_message_domain_repo_id FOREIGN KEY (domain_repo_id) REFERENCES public."Domain"(repo_id) DEFERRABLE INITIALLY DEFERRED; --