From 4e4c0adf5ef291246ac767c643254fee02493003 Mon Sep 17 00:00:00 2001 From: Shicong Huang Date: Tue, 21 Apr 2020 15:40:16 -0400 Subject: [PATCH] Add JPA annotations to ContactResource and generate schema (#547) * Add JPA annotations to ContactResource and generate schema * Resolve comments * Resolve comments * Manually add foreign key constraints * Run with junit5 * Rebase on HEAD * Fix DomainBaseSqlTest --- .../google/registry/model/EppResource.java | 6 +- .../model/contact/ContactAddress.java | 8 +- .../model/contact/ContactAuthInfo.java | 1 + .../model/contact/ContactPhoneNumber.java | 4 +- .../model/contact/ContactResource.java | 93 ++++++++- .../registry/model/contact/Disclose.java | 9 +- .../registry/model/contact/PostalInfo.java | 9 +- .../registry/model/eppcommon/PhoneNumber.java | 6 + .../model/eppcommon/PresenceMarker.java | 2 + .../persistence/EntityCallbacksListener.java | 4 + .../PostalInfoChoiceListConverter.java | 36 ++++ .../main/resources/META-INF/persistence.xml | 2 + .../model/contact/ContactResourceTest.java | 181 +++++++++++------- .../model/domain/DomainBaseSqlTest.java | 50 ++--- .../integration/SqlIntegrationTestSuite.java | 2 + .../google/registry/testing/SqlHelper.java | 21 ++ .../sql/flyway/V23__create_contact.sql | 107 +++++++++++ .../sql/schema/db-schema.sql.generated | 68 ++++++- .../resources/sql/schema/nomulus.golden.sql | 165 +++++++++++++++- 19 files changed, 641 insertions(+), 133 deletions(-) create mode 100644 core/src/main/java/google/registry/persistence/converter/PostalInfoChoiceListConverter.java create mode 100644 db/src/main/resources/sql/flyway/V23__create_contact.sql diff --git a/core/src/main/java/google/registry/model/EppResource.java b/core/src/main/java/google/registry/model/EppResource.java index 18241ab8c..845052291 100644 --- a/core/src/main/java/google/registry/model/EppResource.java +++ b/core/src/main/java/google/registry/model/EppResource.java @@ -70,9 +70,11 @@ public abstract class EppResource extends BackupGroupRoot implements Buildable { /** The ID of the registrar that is currently sponsoring this resource. */ @Index + @Column(nullable = false) String currentSponsorClientId; /** The ID of the registrar that created this resource. */ + @Column(nullable = false) String creationClientId; /** @@ -88,7 +90,9 @@ public abstract class EppResource extends BackupGroupRoot implements Buildable { // Map the method to XML, not the field, because if we map the field (with an adaptor class) it // will never be omitted from the xml even if the timestamp inside creationTime is null and we // return null from the adaptor. (Instead it gets written as an empty tag.) - @Index CreateAutoTimestamp creationTime = CreateAutoTimestamp.create(null); + @Column(nullable = false) + @Index + CreateAutoTimestamp creationTime = CreateAutoTimestamp.create(null); /** * The time when this resource was or will be deleted. diff --git a/core/src/main/java/google/registry/model/contact/ContactAddress.java b/core/src/main/java/google/registry/model/contact/ContactAddress.java index bacf2e467..7aab6e6d7 100644 --- a/core/src/main/java/google/registry/model/contact/ContactAddress.java +++ b/core/src/main/java/google/registry/model/contact/ContactAddress.java @@ -16,13 +16,14 @@ package google.registry.model.contact; import com.googlecode.objectify.annotation.Embed; import google.registry.model.eppcommon.Address; +import javax.persistence.Embeddable; /** * EPP Contact Address * - *

This class is embedded inside the {@link PostalInfo} of an EPP contact to hold its - * address. The fields are all defined in parent class {@link Address}, but the subclass is still - * necessary to pick up the contact namespace. + *

This class is embedded inside the {@link PostalInfo} of an EPP contact to hold its address. + * The fields are all defined in parent class {@link Address}, but the subclass is still necessary + * to pick up the contact namespace. * *

This does not implement {@code Overlayable} because it is intended to be bulk replaced on * update. @@ -30,6 +31,7 @@ import google.registry.model.eppcommon.Address; * @see PostalInfo */ @Embed +@Embeddable public class ContactAddress extends Address { /** Builder for {@link ContactAddress}. */ diff --git a/core/src/main/java/google/registry/model/contact/ContactAuthInfo.java b/core/src/main/java/google/registry/model/contact/ContactAuthInfo.java index 8d3fa3808..56c4d02d6 100644 --- a/core/src/main/java/google/registry/model/contact/ContactAuthInfo.java +++ b/core/src/main/java/google/registry/model/contact/ContactAuthInfo.java @@ -20,6 +20,7 @@ import javax.xml.bind.annotation.XmlType; /** A version of authInfo specifically for contacts. */ @Embed +@javax.persistence.Embeddable @XmlType(namespace = "urn:ietf:params:xml:ns:contact-1.0") public class ContactAuthInfo extends AuthInfo { public static ContactAuthInfo create(PasswordAuth pw) { diff --git a/core/src/main/java/google/registry/model/contact/ContactPhoneNumber.java b/core/src/main/java/google/registry/model/contact/ContactPhoneNumber.java index dbba4d2b2..750622b86 100644 --- a/core/src/main/java/google/registry/model/contact/ContactPhoneNumber.java +++ b/core/src/main/java/google/registry/model/contact/ContactPhoneNumber.java @@ -16,17 +16,19 @@ package google.registry.model.contact; import com.googlecode.objectify.annotation.Embed; import google.registry.model.eppcommon.PhoneNumber; +import javax.persistence.Embeddable; /** * EPP Contact Phone Number * *

This class is embedded inside a {@link ContactResource} hold the phone number of an EPP - * contact. The fields are all defined in the parent class {@link PhoneNumber}, but the subclass is + * contact. The fields are all defined in the parent class {@link PhoneNumber}, but the subclass is * still necessary to pick up the contact namespace. * * @see ContactResource */ @Embed +@Embeddable public class ContactPhoneNumber extends PhoneNumber { /** Builder for {@link ContactPhoneNumber}. */ diff --git a/core/src/main/java/google/registry/model/contact/ContactResource.java b/core/src/main/java/google/registry/model/contact/ContactResource.java index 9c09f165f..f459677ae 100644 --- a/core/src/main/java/google/registry/model/contact/ContactResource.java +++ b/core/src/main/java/google/registry/model/contact/ContactResource.java @@ -33,6 +33,11 @@ import google.registry.model.transfer.TransferData; import java.util.Objects; import java.util.Optional; import java.util.stream.Stream; +import javax.persistence.AttributeOverride; +import javax.persistence.AttributeOverrides; +import javax.persistence.Column; +import javax.persistence.Embedded; +import javax.persistence.Transient; import javax.xml.bind.annotation.XmlElement; import org.joda.time.DateTime; @@ -43,9 +48,19 @@ import org.joda.time.DateTime; */ @ReportedOn @Entity +@javax.persistence.Entity(name = "Contact") +@javax.persistence.Table( + name = "Contact", + indexes = { + @javax.persistence.Index(columnList = "creationTime"), + @javax.persistence.Index(columnList = "currentSponsorClientId"), + @javax.persistence.Index(columnList = "deletionTime"), + @javax.persistence.Index(columnList = "contactId", unique = true), + @javax.persistence.Index(columnList = "searchName") + }) @ExternalMessagingName("contact") -public class ContactResource extends EppResource implements - ForeignKeyedEppResource, ResourceWithTransferData { +public class ContactResource extends EppResource + implements ForeignKeyedEppResource, ResourceWithTransferData { /** * Unique identifier for this contact. @@ -61,13 +76,55 @@ public class ContactResource extends EppResource implements * US-ASCII character set. Personal info; cleared by {@link Builder#wipeOut}. */ @IgnoreSave(IfNull.class) + @Embedded + @AttributeOverrides({ + @AttributeOverride(name = "name", column = @Column(name = "addr_local_name")), + @AttributeOverride(name = "org", column = @Column(name = "addr_local_org")), + @AttributeOverride(name = "type", column = @Column(name = "addr_local_type")), + @AttributeOverride( + name = "address.streetLine1", + column = @Column(name = "addr_local_street_line1")), + @AttributeOverride( + name = "address.streetLine2", + column = @Column(name = "addr_local_street_line2")), + @AttributeOverride( + name = "address.streetLine3", + column = @Column(name = "addr_local_street_line3")), + @AttributeOverride(name = "address.city", column = @Column(name = "addr_local_city")), + @AttributeOverride(name = "address.state", column = @Column(name = "addr_local_state")), + @AttributeOverride(name = "address.zip", column = @Column(name = "addr_local_zip")), + @AttributeOverride( + name = "address.countryCode", + column = @Column(name = "addr_local_country_code")) + }) PostalInfo localizedPostalInfo; /** - * Internationalized postal info for the contact. Personal info; cleared by - * {@link Builder#wipeOut}. + * Internationalized postal info for the contact. Personal info; cleared by {@link + * Builder#wipeOut}. */ @IgnoreSave(IfNull.class) + @Embedded + @AttributeOverrides({ + @AttributeOverride(name = "name", column = @Column(name = "addr_i18n_name")), + @AttributeOverride(name = "org", column = @Column(name = "addr_i18n_org")), + @AttributeOverride(name = "type", column = @Column(name = "addr_i18n_type")), + @AttributeOverride( + name = "address.streetLine1", + column = @Column(name = "addr_i18n_street_line1")), + @AttributeOverride( + name = "address.streetLine2", + column = @Column(name = "addr_i18n_street_line2")), + @AttributeOverride( + name = "address.streetLine3", + column = @Column(name = "addr_i18n_street_line3")), + @AttributeOverride(name = "address.city", column = @Column(name = "addr_i18n_city")), + @AttributeOverride(name = "address.state", column = @Column(name = "addr_i18n_state")), + @AttributeOverride(name = "address.zip", column = @Column(name = "addr_i18n_zip")), + @AttributeOverride( + name = "address.countryCode", + column = @Column(name = "addr_i18n_country_code")) + }) PostalInfo internationalizedPostalInfo; /** @@ -80,10 +137,20 @@ public class ContactResource extends EppResource implements /** Contact’s voice number. Personal info; cleared by {@link Builder#wipeOut}. */ @IgnoreSave(IfNull.class) + @Embedded + @AttributeOverrides({ + @AttributeOverride(name = "phoneNumber", column = @Column(name = "voice_phone_number")), + @AttributeOverride(name = "extension", column = @Column(name = "voice_phone_extension")), + }) ContactPhoneNumber voice; /** Contact’s fax number. Personal info; cleared by {@link Builder#wipeOut}. */ @IgnoreSave(IfNull.class) + @Embedded + @AttributeOverrides({ + @AttributeOverride(name = "phoneNumber", column = @Column(name = "fax_phone_number")), + @AttributeOverride(name = "extension", column = @Column(name = "fax_phone_extension")), + }) ContactPhoneNumber fax; /** Contact’s email address. Personal info; cleared by {@link Builder#wipeOut}. */ @@ -91,10 +158,16 @@ public class ContactResource extends EppResource implements String email; /** Authorization info (aka transfer secret) of the contact. */ + @Embedded + @AttributeOverrides({ + @AttributeOverride(name = "pw.value", column = @Column(name = "auth_info_value")), + @AttributeOverride(name = "pw.repoId", column = @Column(name = "auth_info_repo_id")), + }) ContactAuthInfo authInfo; /** Data about any pending or past transfers on this contact. */ - TransferData transferData; + // TODO(b/153363295): Figure out how to persist transfer data + @Transient TransferData transferData; /** * The time that this resource was last transferred. @@ -107,6 +180,16 @@ public class ContactResource extends EppResource implements // the wipeOut() function, so that data is not kept around for deleted contacts. /** Disclosure policy. */ + @Embedded + @AttributeOverrides({ + @AttributeOverride(name = "name", column = @Column(name = "disclose_types_name")), + @AttributeOverride(name = "org", column = @Column(name = "disclose_types_org")), + @AttributeOverride(name = "addr", column = @Column(name = "disclose_types_addr")), + @AttributeOverride(name = "flag", column = @Column(name = "disclose_mode_flag")), + @AttributeOverride(name = "voice.marked", column = @Column(name = "disclose_show_voice")), + @AttributeOverride(name = "fax.marked", column = @Column(name = "disclose_show_fax")), + @AttributeOverride(name = "email.marked", column = @Column(name = "disclose_show_email")) + }) Disclose disclose; public String getContactId() { diff --git a/core/src/main/java/google/registry/model/contact/Disclose.java b/core/src/main/java/google/registry/model/contact/Disclose.java index f907b5f05..d0003afd9 100644 --- a/core/src/main/java/google/registry/model/contact/Disclose.java +++ b/core/src/main/java/google/registry/model/contact/Disclose.java @@ -22,11 +22,14 @@ import google.registry.model.Buildable; import google.registry.model.ImmutableObject; import google.registry.model.eppcommon.PresenceMarker; import java.util.List; +import javax.persistence.Embeddable; +import javax.persistence.Embedded; import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.XmlType; /** The "discloseType" from {@link "http://tools.ietf.org/html/rfc5733"}. */ @Embed +@Embeddable @XmlType(propOrder = {"name", "org", "addr", "voice", "fax", "email"}) public class Disclose extends ImmutableObject { @@ -36,11 +39,11 @@ public class Disclose extends ImmutableObject { List addr; - PresenceMarker voice; + @Embedded PresenceMarker voice; - PresenceMarker fax; + @Embedded PresenceMarker fax; - PresenceMarker email; + @Embedded PresenceMarker email; @XmlAttribute Boolean flag; diff --git a/core/src/main/java/google/registry/model/contact/PostalInfo.java b/core/src/main/java/google/registry/model/contact/PostalInfo.java index 6e43dd7e2..d71aed052 100644 --- a/core/src/main/java/google/registry/model/contact/PostalInfo.java +++ b/core/src/main/java/google/registry/model/contact/PostalInfo.java @@ -21,6 +21,9 @@ import google.registry.model.Buildable; import google.registry.model.Buildable.Overlayable; import google.registry.model.ImmutableObject; import java.util.Optional; +import javax.persistence.Embeddable; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlEnumValue; @@ -29,10 +32,11 @@ import javax.xml.bind.annotation.adapters.NormalizedStringAdapter; import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; /** - * Implementation of both "postalInfoType" and "chgPostalInfoType" from - * {@link "http://tools.ietf.org/html/rfc5733"}. + * Implementation of both "postalInfoType" and "chgPostalInfoType" from {@link + * "http://tools.ietf.org/html/rfc5733"}. */ @Embed +@Embeddable @XmlType(propOrder = {"name", "org", "address", "type"}) public class PostalInfo extends ImmutableObject implements Overlayable { @@ -53,6 +57,7 @@ public class PostalInfo extends ImmutableObject implements Overlayable + * *

"Contact telephone number structure is derived from structures defined in [ITU.E164.2005]. * Telephone numbers described in this mapping are character strings that MUST begin with a plus * sign ("+", ASCII value 0x002B), followed by a country code defined in [ITU.E164.2005], followed * by a dot (".", ASCII value 0x002E), followed by a sequence of digits representing the telephone * number. An optional "x" attribute is provided to note telephone extension information." + * * * * @see google.registry.model.contact.ContactPhoneNumber * @see google.registry.model.mark.MarkPhoneNumber */ @XmlTransient +@Embeddable +@MappedSuperclass public class PhoneNumber extends ImmutableObject { @XmlValue diff --git a/core/src/main/java/google/registry/model/eppcommon/PresenceMarker.java b/core/src/main/java/google/registry/model/eppcommon/PresenceMarker.java index dc65565f9..2eb4f0060 100644 --- a/core/src/main/java/google/registry/model/eppcommon/PresenceMarker.java +++ b/core/src/main/java/google/registry/model/eppcommon/PresenceMarker.java @@ -17,6 +17,7 @@ package google.registry.model.eppcommon; import com.googlecode.objectify.annotation.Embed; import google.registry.model.ImmutableObject; import java.io.Serializable; +import javax.persistence.Embeddable; import javax.xml.bind.annotation.XmlTransient; /** @@ -26,6 +27,7 @@ import javax.xml.bind.annotation.XmlTransient; * {@code }, and will unmarshal always to {@code }. */ @Embed +@Embeddable public class PresenceMarker extends ImmutableObject implements Serializable { @XmlTransient boolean marked = true; diff --git a/core/src/main/java/google/registry/persistence/EntityCallbacksListener.java b/core/src/main/java/google/registry/persistence/EntityCallbacksListener.java index 931b24976..776b1e133 100644 --- a/core/src/main/java/google/registry/persistence/EntityCallbacksListener.java +++ b/core/src/main/java/google/registry/persistence/EntityCallbacksListener.java @@ -18,6 +18,7 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.lang.reflect.Modifier; import java.util.Arrays; import java.util.Objects; import java.util.stream.Stream; @@ -31,6 +32,7 @@ import javax.persistence.PostUpdate; import javax.persistence.PrePersist; import javax.persistence.PreRemove; import javax.persistence.PreUpdate; +import javax.persistence.Transient; /** * A listener class to invoke entity callbacks in cases where Hibernate doesn't invoke the callback @@ -167,10 +169,12 @@ public class EntityCallbacksListener { private Stream findEmbeddedProperties(Object object, Class clazz) { return Arrays.stream(clazz.getDeclaredFields()) + .filter(field -> !field.isAnnotationPresent(Transient.class)) .filter( field -> field.isAnnotationPresent(Embedded.class) || field.getType().isAnnotationPresent(Embeddable.class)) + .filter(field -> !Modifier.isStatic(field.getModifiers())) .map(field -> getFieldObject(field, object)) .filter(Objects::nonNull); } diff --git a/core/src/main/java/google/registry/persistence/converter/PostalInfoChoiceListConverter.java b/core/src/main/java/google/registry/persistence/converter/PostalInfoChoiceListConverter.java new file mode 100644 index 000000000..a92236b24 --- /dev/null +++ b/core/src/main/java/google/registry/persistence/converter/PostalInfoChoiceListConverter.java @@ -0,0 +1,36 @@ +// 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.persistence.converter; + +import google.registry.model.contact.Disclose.PostalInfoChoice; +import google.registry.model.contact.PostalInfo; +import java.util.List; +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; + +/** JPA {@link AttributeConverter} for storing/retrieving {@link List < PostalInfoChoice >}. */ +@Converter(autoApply = true) +public class PostalInfoChoiceListConverter extends StringListConverterBase { + + @Override + String toString(PostalInfoChoice element) { + return element.getType().name(); + } + + @Override + PostalInfoChoice fromString(String value) { + return PostalInfoChoice.create(PostalInfo.Type.valueOf(value)); + } +} diff --git a/core/src/main/resources/META-INF/persistence.xml b/core/src/main/resources/META-INF/persistence.xml index 0e2bdd5ea..2098d2b9b 100644 --- a/core/src/main/resources/META-INF/persistence.xml +++ b/core/src/main/resources/META-INF/persistence.xml @@ -19,6 +19,7 @@ * Move tests to another (sub)project. This is not a big problem, but feels unnatural. * Use Hibernate's ServiceRegistry for bootstrapping (not JPA-compliant) --> + google.registry.model.contact.ContactResource google.registry.model.domain.DomainBase google.registry.model.host.HostResource google.registry.model.registrar.Registrar @@ -41,6 +42,7 @@ google.registry.persistence.converter.CurrencyUnitConverter google.registry.persistence.converter.DateTimeConverter google.registry.persistence.converter.DurationConverter + google.registry.persistence.converter.PostalInfoChoiceListConverter google.registry.persistence.converter.RegistrarPocSetConverter google.registry.persistence.converter.StatusValueSetConverter google.registry.persistence.converter.StringListConverter diff --git a/core/src/test/java/google/registry/model/contact/ContactResourceTest.java b/core/src/test/java/google/registry/model/contact/ContactResourceTest.java index 687eedfeb..897bc2cc1 100644 --- a/core/src/test/java/google/registry/model/contact/ContactResourceTest.java +++ b/core/src/test/java/google/registry/model/contact/ContactResourceTest.java @@ -17,10 +17,13 @@ package google.registry.model.contact; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth8.assertThat; import static google.registry.model.EppResourceUtils.loadByForeignKey; +import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm; import static google.registry.testing.ContactResourceSubject.assertAboutContacts; import static google.registry.testing.DatastoreHelper.cloneAndSetAutoTimestamps; import static google.registry.testing.DatastoreHelper.createTld; import static google.registry.testing.DatastoreHelper.persistResource; +import static google.registry.testing.SqlHelper.assertThrowForeignKeyViolation; +import static google.registry.testing.SqlHelper.saveRegistrar; import static google.registry.util.DateTimeUtils.END_OF_TIME; import static org.junit.Assert.assertThrows; @@ -37,87 +40,119 @@ import google.registry.model.eppcommon.StatusValue; import google.registry.model.eppcommon.Trid; import google.registry.model.transfer.TransferData; import google.registry.model.transfer.TransferStatus; -import org.junit.Before; -import org.junit.Test; +import google.registry.persistence.VKey; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; /** Unit tests for {@link ContactResource}. */ public class ContactResourceTest extends EntityTestCase { + ContactResource originalContact; ContactResource contactResource; - @Before + public ContactResourceTest() { + super(true); + } + + @BeforeEach public void setUp() { createTld("foobar"); + originalContact = + new ContactResource.Builder() + .setContactId("contact_id") + .setRepoId("1-FOOBAR") + .setCreationClientId("registrar1") + .setLastEppUpdateTime(fakeClock.nowUtc()) + .setLastEppUpdateClientId("registrar2") + .setLastTransferTime(fakeClock.nowUtc()) + .setPersistedCurrentSponsorClientId("registrar3") + .setLocalizedPostalInfo( + new PostalInfo.Builder() + .setType(Type.LOCALIZED) + .setAddress( + new ContactAddress.Builder() + .setStreet(ImmutableList.of("111 8th Ave", "4th Floor")) + .setCity("New York") + .setState("NY") + .setZip("10011") + .setCountryCode("US") + .build()) + .build()) + .setInternationalizedPostalInfo( + new PostalInfo.Builder() + .setType(Type.INTERNATIONALIZED) + .setAddress( + new ContactAddress.Builder() + .setStreet(ImmutableList.of("111 8th Ave", "4th Floor")) + .setCity("New York") + .setState("NY") + .setZip("10011") + .setCountryCode("US") + .build()) + .build()) + .setVoiceNumber(new ContactPhoneNumber.Builder().setPhoneNumber("867-5309").build()) + .setFaxNumber( + new ContactPhoneNumber.Builder() + .setPhoneNumber("867-5309") + .setExtension("1000") + .build()) + .setEmailAddress("jenny@example.com") + .setAuthInfo(ContactAuthInfo.create(PasswordAuth.create("passw0rd"))) + .setDisclose( + new Disclose.Builder() + .setVoice(new PresenceMarker()) + .setEmail(new PresenceMarker()) + .setFax(new PresenceMarker()) + .setFlag(true) + .setAddrs(ImmutableList.of(PostalInfoChoice.create(Type.INTERNATIONALIZED))) + .setNames(ImmutableList.of(PostalInfoChoice.create(Type.INTERNATIONALIZED))) + .setOrgs(ImmutableList.of(PostalInfoChoice.create(Type.INTERNATIONALIZED))) + .build()) + .setStatusValues(ImmutableSet.of(StatusValue.OK)) + .setTransferData( + new TransferData.Builder() + .setGainingClientId("gaining") + .setLosingClientId("losing") + .setPendingTransferExpirationTime(fakeClock.nowUtc()) + .setServerApproveEntities( + ImmutableSet.of(Key.create(BillingEvent.OneTime.class, 1))) + .setTransferRequestTime(fakeClock.nowUtc()) + .setTransferStatus(TransferStatus.SERVER_APPROVED) + .setTransferRequestTrid(Trid.create("client-trid", "server-trid")) + .build()) + .build(); // Set up a new persisted ContactResource entity. - contactResource = - persistResource( - cloneAndSetAutoTimestamps( - new ContactResource.Builder() - .setContactId("contact_id") - .setRepoId("1-FOOBAR") - .setCreationClientId("a registrar") - .setLastEppUpdateTime(fakeClock.nowUtc()) - .setLastEppUpdateClientId("another registrar") - .setLastTransferTime(fakeClock.nowUtc()) - .setPersistedCurrentSponsorClientId("a third registrar") - .setLocalizedPostalInfo( - new PostalInfo.Builder() - .setType(Type.LOCALIZED) - .setAddress( - new ContactAddress.Builder() - .setStreet(ImmutableList.of("111 8th Ave", "4th Floor")) - .setCity("New York") - .setState("NY") - .setZip("10011") - .setCountryCode("US") - .build()) - .build()) - .setInternationalizedPostalInfo( - new PostalInfo.Builder() - .setType(Type.INTERNATIONALIZED) - .setAddress( - new ContactAddress.Builder() - .setStreet(ImmutableList.of("111 8th Ave", "4th Floor")) - .setCity("New York") - .setState("NY") - .setZip("10011") - .setCountryCode("US") - .build()) - .build()) - .setVoiceNumber( - new ContactPhoneNumber.Builder().setPhoneNumber("867-5309").build()) - .setFaxNumber( - new ContactPhoneNumber.Builder() - .setPhoneNumber("867-5309") - .setExtension("1000") - .build()) - .setEmailAddress("jenny@example.com") - .setAuthInfo(ContactAuthInfo.create(PasswordAuth.create("passw0rd"))) - .setDisclose( - new Disclose.Builder() - .setVoice(new PresenceMarker()) - .setEmail(new PresenceMarker()) - .setFax(new PresenceMarker()) - .setFlag(true) - .setAddrs( - ImmutableList.of(PostalInfoChoice.create(Type.INTERNATIONALIZED))) - .setNames( - ImmutableList.of(PostalInfoChoice.create(Type.INTERNATIONALIZED))) - .setOrgs( - ImmutableList.of(PostalInfoChoice.create(Type.INTERNATIONALIZED))) - .build()) - .setStatusValues(ImmutableSet.of(StatusValue.OK)) - .setTransferData( - new TransferData.Builder() - .setGainingClientId("gaining") - .setLosingClientId("losing") - .setPendingTransferExpirationTime(fakeClock.nowUtc()) - .setServerApproveEntities( - ImmutableSet.of(Key.create(BillingEvent.OneTime.class, 1))) - .setTransferRequestTime(fakeClock.nowUtc()) - .setTransferStatus(TransferStatus.SERVER_APPROVED) - .setTransferRequestTrid(Trid.create("client-trid", "server-trid")) - .build()) - .build())); + contactResource = persistResource(cloneAndSetAutoTimestamps(originalContact)); + } + + @Test + public void testCloudSqlPersistence_failWhenViolateForeignKeyConstraint() { + assertThrowForeignKeyViolation(() -> jpaTm().transact(() -> jpaTm().saveNew(originalContact))); + } + + @Test + public void testCloudSqlPersistence_succeed() { + saveRegistrar("registrar1"); + saveRegistrar("registrar2"); + saveRegistrar("registrar3"); + jpaTm().transact(() -> jpaTm().saveNew(originalContact)); + ContactResource persisted = + jpaTm() + .transact( + () -> + jpaTm() + .load(VKey.createSql(ContactResource.class, originalContact.getRepoId()))) + .get(); + // TODO(b/153378849): Remove the hard code for postal info after resolving the issue that + // @PostLoad doesn't work in Address + ContactResource fixed = + originalContact + .asBuilder() + .setCreationTime(persisted.getCreationTime()) + .setInternationalizedPostalInfo(persisted.getInternationalizedPostalInfo()) + .setLocalizedPostalInfo(persisted.getLocalizedPostalInfo()) + .setTransferData(null) + .build(); + assertThat(persisted).isEqualTo(fixed); } @Test 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 e4c5b6ebb..c41a051f5 100644 --- a/core/src/test/java/google/registry/model/domain/DomainBaseSqlTest.java +++ b/core/src/test/java/google/registry/model/domain/DomainBaseSqlTest.java @@ -16,9 +16,10 @@ package google.registry.model.domain; import static com.google.common.truth.Truth.assertThat; import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm; +import static google.registry.testing.SqlHelper.assertThrowForeignKeyViolation; +import static google.registry.testing.SqlHelper.saveRegistrar; import static google.registry.util.DateTimeUtils.START_OF_TIME; import static org.joda.time.DateTimeZone.UTC; -import static org.junit.Assert.assertThrows; import com.google.common.collect.ImmutableSet; import com.googlecode.objectify.Key; @@ -34,9 +35,7 @@ import google.registry.persistence.transaction.JpaTestRules; import google.registry.persistence.transaction.JpaTestRules.JpaIntegrationWithCoverageExtension; import google.registry.testing.DatastoreEntityExtension; import google.registry.testing.FakeClock; -import java.sql.SQLException; import javax.persistence.EntityManager; -import javax.persistence.RollbackException; import org.joda.time.DateTime; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Order; @@ -73,9 +72,9 @@ public class DomainBaseSqlTest { new DomainBase.Builder() .setFullyQualifiedDomainName("example.com") .setRepoId("4-COM") - .setCreationClientId("a registrar") + .setCreationClientId("registrar1") .setLastEppUpdateTime(fakeClock.nowUtc()) - .setLastEppUpdateClientId("AnotherRegistrar") + .setLastEppUpdateClientId("registrar2") .setLastTransferTime(fakeClock.nowUtc()) .setNameservers(host1VKey) .setStatusValues( @@ -89,7 +88,7 @@ public class DomainBaseSqlTest { .setRegistrant(contactKey) .setContacts(ImmutableSet.of(DesignatedContact.create(Type.ADMIN, contact2Key))) .setSubordinateHosts(ImmutableSet.of("ns1.example.com")) - .setPersistedCurrentSponsorClientId("losing") + .setPersistedCurrentSponsorClientId("registrar3") .setRegistrationExpirationTime(fakeClock.nowUtc().plusYears(1)) .setAuthInfo(DomainAuthInfo.create(PasswordAuth.create("password"))) .setDsData(ImmutableSet.of(DelegationSignerData.create(1, 2, 3, new byte[] {0, 1, 2}))) @@ -102,11 +101,17 @@ public class DomainBaseSqlTest { new HostResource.Builder() .setRepoId("host1") .setFullyQualifiedHostName("ns1.example.com") + .setCreationClientId("registrar1") + .setPersistedCurrentSponsorClientId("registrar2") .build(); } @Test public void testDomainBasePersistence() { + saveRegistrar("registrar1"); + saveRegistrar("registrar2"); + saveRegistrar("registrar3"); + jpaTm() .transact( () -> { @@ -147,28 +152,15 @@ public class DomainBaseSqlTest { @Test public void testForeignKeyConstraints() { - Exception e = - assertThrows( - RollbackException.class, - () -> { - jpaTm() - .transact( - () -> { - // Persist the domain without the associated host object. - EntityManager em = jpaTm().getEntityManager(); - em.persist(domain); - }); - }); - assertThat(e) - .hasCauseThat() // ConstraintViolationException - .hasCauseThat() // ConstraintViolationException - .hasCauseThat() - .isInstanceOf(SQLException.class); - assertThat(e) - .hasCauseThat() // ConstraintViolationException - .hasCauseThat() // ConstraintViolationException - .hasCauseThat() - .hasMessageThat() - .contains("\"DomainHost\" violates foreign key constraint \"fk_domainhost_host"); + assertThrowForeignKeyViolation( + () -> { + jpaTm() + .transact( + () -> { + // Persist the domain without the associated host object. + EntityManager em = jpaTm().getEntityManager(); + em.persist(domain); + }); + }); } } diff --git a/core/src/test/java/google/registry/schema/integration/SqlIntegrationTestSuite.java b/core/src/test/java/google/registry/schema/integration/SqlIntegrationTestSuite.java index c7d16f558..935b3b194 100644 --- a/core/src/test/java/google/registry/schema/integration/SqlIntegrationTestSuite.java +++ b/core/src/test/java/google/registry/schema/integration/SqlIntegrationTestSuite.java @@ -16,6 +16,7 @@ package google.registry.schema.integration; import static com.google.common.truth.Truth.assert_; +import google.registry.model.contact.ContactResourceTest; import google.registry.model.domain.DomainBaseSqlTest; import google.registry.model.registry.RegistryLockDaoTest; import google.registry.persistence.transaction.JpaEntityCoverage; @@ -68,6 +69,7 @@ import org.junit.runner.RunWith; // BeforeSuiteTest must be the first entry. See class javadoc for details. BeforeSuiteTest.class, ClaimsListDaoTest.class, + ContactResourceTest.class, CursorDaoTest.class, DomainBaseSqlTest.class, LockDaoTest.class, diff --git a/core/src/test/java/google/registry/testing/SqlHelper.java b/core/src/test/java/google/registry/testing/SqlHelper.java index d24af781f..e5f6d3200 100644 --- a/core/src/test/java/google/registry/testing/SqlHelper.java +++ b/core/src/test/java/google/registry/testing/SqlHelper.java @@ -14,12 +14,19 @@ package google.registry.testing; +import static com.google.common.truth.Truth.assertThat; import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm; +import static google.registry.testing.AppEngineRule.makeRegistrar1; +import static org.junit.Assert.assertThrows; +import com.google.common.base.Throwables; import com.google.common.collect.ImmutableList; import google.registry.model.registry.RegistryLockDao; import google.registry.schema.domain.RegistryLock; +import java.sql.SQLException; import java.util.Optional; +import javax.persistence.RollbackException; +import org.junit.function.ThrowingRunnable; /** Static utils for setting up and retrieving test resources from the SQL database. */ public class SqlHelper { @@ -52,5 +59,19 @@ public class SqlHelper { return jpaTm().transact(() -> RegistryLockDao.getByRevisionId(revisionId)); } + public static void saveRegistrar(String clientId) { + jpaTm() + .transact( + () -> jpaTm().saveNew(makeRegistrar1().asBuilder().setClientId(clientId).build())); + } + + public static void assertThrowForeignKeyViolation(ThrowingRunnable runnable) { + RollbackException thrown = assertThrows(RollbackException.class, runnable); + assertThat(Throwables.getRootCause(thrown)).isInstanceOf(SQLException.class); + assertThat(Throwables.getRootCause(thrown)) + .hasMessageThat() + .contains("violates foreign key constraint"); + } + private SqlHelper() {} } diff --git a/db/src/main/resources/sql/flyway/V23__create_contact.sql b/db/src/main/resources/sql/flyway/V23__create_contact.sql new file mode 100644 index 000000000..5bbd7f646 --- /dev/null +++ b/db/src/main/resources/sql/flyway/V23__create_contact.sql @@ -0,0 +1,107 @@ +-- 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 "Contact" ( + repo_id text not null, + creation_client_id text not null, + creation_time timestamptz not null, + current_sponsor_client_id text not null, + deletion_time timestamptz, + last_epp_update_client_id text, + last_epp_update_time timestamptz, + statuses text[], + auth_info_repo_id text, + auth_info_value text, + contact_id text, + disclose_types_addr text[], + disclose_show_email boolean, + disclose_show_fax boolean, + disclose_mode_flag boolean, + disclose_types_name text[], + disclose_types_org text[], + disclose_show_voice boolean, + email text, + fax_phone_extension text, + fax_phone_number text, + addr_i18n_city text, + addr_i18n_country_code text, + addr_i18n_state text, + addr_i18n_street_line1 text, + addr_i18n_street_line2 text, + addr_i18n_street_line3 text, + addr_i18n_zip text, + addr_i18n_name text, + addr_i18n_org text, + addr_i18n_type text, + last_transfer_time timestamptz, + addr_local_city text, + addr_local_country_code text, + addr_local_state text, + addr_local_street_line1 text, + addr_local_street_line2 text, + addr_local_street_line3 text, + addr_local_zip text, + addr_local_name text, + addr_local_org text, + addr_local_type text, + search_name text, + voice_phone_extension text, + voice_phone_number text, + primary key (repo_id) +); + +create index IDX3y752kr9uh4kh6uig54vemx0l on "Contact" (creation_time); +create index IDXbn8t4wp85fgxjl8q4ctlscx55 on "Contact" (current_sponsor_client_id); +create index IDXn1f711wicdnooa2mqb7g1m55o on "Contact" (deletion_time); +create index IDX1p3esngcwwu6hstyua6itn6ff on "Contact" (search_name); + +alter table if exists "Contact" + add constraint UKoqd7n4hbx86hvlgkilq75olas unique (contact_id); + +alter table "Domain" alter column creation_time set not null; +alter table "Domain" alter column creation_client_id set not null; +alter table "Domain" alter column current_sponsor_client_id set not null; + +drop index IDX8ffrqm27qtj20jac056j7yq07; +create index IDXkjt9yaq92876dstimd93hwckh on "Domain" (current_sponsor_client_id); + +alter table if exists "Contact" + add constraint FK1sfyj7o7954prbn1exk7lpnoe + foreign key (creation_client_id) + references "Registrar"; + +alter table if exists "Contact" + add constraint FK93c185fx7chn68uv7nl6uv2s0 + foreign key (current_sponsor_client_id) + references "Registrar"; + +alter table if exists "Contact" + add constraint FKmb7tdiv85863134w1wogtxrb2 + foreign key (last_epp_update_client_id) + references "Registrar"; + +alter table if exists "Domain" + add constraint FK2jc69qyg2tv9hhnmif6oa1cx1 + foreign key (creation_client_id) + references "Registrar"; + +alter table if exists "Domain" + add constraint FK2u3srsfbei272093m3b3xwj23 + foreign key (current_sponsor_client_id) + references "Registrar"; + +alter table if exists "Domain" + add constraint FKjc0r9r5y1lfbt4gpbqw4wsuvq + foreign key (last_epp_update_client_id) + references "Registrar"; 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 76a26d548..acf9cc65e 100644 --- a/db/src/main/resources/sql/schema/db-schema.sql.generated +++ b/db/src/main/resources/sql/schema/db-schema.sql.generated @@ -26,6 +26,55 @@ primary key (revision_id) ); + create table "Contact" ( + repo_id text not null, + creation_client_id text not null, + creation_time timestamptz not null, + current_sponsor_client_id text not null, + deletion_time timestamptz, + last_epp_update_client_id text, + last_epp_update_time timestamptz, + statuses text[], + auth_info_repo_id text, + auth_info_value text, + contact_id text, + disclose_types_addr text[], + disclose_show_email boolean, + disclose_show_fax boolean, + disclose_mode_flag boolean, + disclose_types_name text[], + disclose_types_org text[], + disclose_show_voice boolean, + email text, + fax_phone_extension text, + fax_phone_number text, + addr_i18n_city text, + addr_i18n_country_code text, + addr_i18n_state text, + addr_i18n_street_line1 text, + addr_i18n_street_line2 text, + addr_i18n_street_line3 text, + addr_i18n_zip text, + addr_i18n_name text, + addr_i18n_org text, + addr_i18n_type text, + last_transfer_time timestamptz, + addr_local_city text, + addr_local_country_code text, + addr_local_state text, + addr_local_street_line1 text, + addr_local_street_line2 text, + addr_local_street_line3 text, + addr_local_zip text, + addr_local_name text, + addr_local_org text, + addr_local_type text, + search_name text, + voice_phone_extension text, + voice_phone_number text, + primary key (repo_id) + ); + create table "Cursor" ( scope text not null, type text not null, @@ -44,9 +93,9 @@ create table "Domain" ( repo_id text not null, - creation_client_id text, - creation_time timestamptz, - current_sponsor_client_id text, + creation_client_id text not null, + creation_time timestamptz not null, + current_sponsor_client_id text not null, deletion_time timestamptz, last_epp_update_client_id text, last_epp_update_time timestamptz, @@ -84,9 +133,9 @@ create table "HostResource" ( repo_id text not null, - creation_client_id text, - creation_time timestamptz, - current_sponsor_client_id text, + creation_client_id text not null, + creation_time timestamptz not null, + current_sponsor_client_id text not null, deletion_time timestamptz, last_epp_update_client_id text, last_epp_update_time timestamptz, @@ -227,6 +276,13 @@ should_publish boolean not null, primary key (revision_id) ); +create index IDX3y752kr9uh4kh6uig54vemx0l on "Contact" (creation_time); +create index IDXbn8t4wp85fgxjl8q4ctlscx55 on "Contact" (current_sponsor_client_id); +create index IDXn1f711wicdnooa2mqb7g1m55o on "Contact" (deletion_time); +create index IDX1p3esngcwwu6hstyua6itn6ff on "Contact" (search_name); + + alter table if exists "Contact" + add constraint UKoqd7n4hbx86hvlgkilq75olas unique (contact_id); create index IDX8nr0ke9mrrx4ewj6pd2ag4rmr on "Domain" (creation_time); create index IDX8ffrqm27qtj20jac056j7yq07 on "Domain" (current_sponsor_client_id); create index IDX5mnf0wn20tno4b9do88j61klr on "Domain" (deletion_time); diff --git a/db/src/main/resources/sql/schema/nomulus.golden.sql b/db/src/main/resources/sql/schema/nomulus.golden.sql index 47fd564b5..d77411065 100644 --- a/db/src/main/resources/sql/schema/nomulus.golden.sql +++ b/db/src/main/resources/sql/schema/nomulus.golden.sql @@ -75,6 +75,59 @@ CREATE SEQUENCE public."ClaimsList_revision_id_seq" ALTER SEQUENCE public."ClaimsList_revision_id_seq" OWNED BY public."ClaimsList".revision_id; +-- +-- Name: Contact; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public."Contact" ( + repo_id text NOT NULL, + creation_client_id text NOT NULL, + creation_time timestamp with time zone NOT NULL, + current_sponsor_client_id text NOT NULL, + deletion_time timestamp with time zone, + last_epp_update_client_id text, + last_epp_update_time timestamp with time zone, + statuses text[], + auth_info_repo_id text, + auth_info_value text, + contact_id text, + disclose_types_addr text[], + disclose_show_email boolean, + disclose_show_fax boolean, + disclose_mode_flag boolean, + disclose_types_name text[], + disclose_types_org text[], + disclose_show_voice boolean, + email text, + fax_phone_extension text, + fax_phone_number text, + addr_i18n_city text, + addr_i18n_country_code text, + addr_i18n_state text, + addr_i18n_street_line1 text, + addr_i18n_street_line2 text, + addr_i18n_street_line3 text, + addr_i18n_zip text, + addr_i18n_name text, + addr_i18n_org text, + addr_i18n_type text, + last_transfer_time timestamp with time zone, + addr_local_city text, + addr_local_country_code text, + addr_local_state text, + addr_local_street_line1 text, + addr_local_street_line2 text, + addr_local_street_line3 text, + addr_local_zip text, + addr_local_name text, + addr_local_org text, + addr_local_type text, + search_name text, + voice_phone_extension text, + voice_phone_number text +); + + -- -- Name: Cursor; Type: TABLE; Schema: public; Owner: - -- @@ -93,9 +146,9 @@ CREATE TABLE public."Cursor" ( CREATE TABLE public."Domain" ( repo_id text NOT NULL, - creation_client_id text, - creation_time timestamp with time zone, - current_sponsor_client_id text, + creation_client_id text NOT NULL, + creation_time timestamp with time zone NOT NULL, + current_sponsor_client_id text NOT NULL, deletion_time timestamp with time zone, last_epp_update_client_id text, last_epp_update_time timestamp with time zone, @@ -414,6 +467,14 @@ ALTER TABLE ONLY public."ClaimsList" ADD CONSTRAINT "ClaimsList_pkey" PRIMARY KEY (revision_id); +-- +-- Name: Contact Contact_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public."Contact" + ADD CONSTRAINT "Contact_pkey" PRIMARY KEY (repo_id); + + -- -- Name: Cursor Cursor_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- @@ -510,6 +571,21 @@ ALTER TABLE ONLY public."RegistryLock" ADD CONSTRAINT idx_registry_lock_repo_id_revision_id UNIQUE (repo_id, revision_id); +-- +-- Name: Contact ukoqd7n4hbx86hvlgkilq75olas; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public."Contact" + ADD CONSTRAINT ukoqd7n4hbx86hvlgkilq75olas UNIQUE (contact_id); + + +-- +-- Name: idx1p3esngcwwu6hstyua6itn6ff; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idx1p3esngcwwu6hstyua6itn6ff ON public."Contact" USING btree (search_name); + + -- -- Name: idx1rcgkdd777bpvj0r94sltwd5y; Type: INDEX; Schema: public; Owner: - -- @@ -517,6 +593,13 @@ ALTER TABLE ONLY public."RegistryLock" CREATE INDEX idx1rcgkdd777bpvj0r94sltwd5y ON public."Domain" USING btree (fully_qualified_domain_name); +-- +-- Name: idx3y752kr9uh4kh6uig54vemx0l; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idx3y752kr9uh4kh6uig54vemx0l ON public."Contact" USING btree (creation_time); + + -- -- Name: idx5mnf0wn20tno4b9do88j61klr; Type: INDEX; Schema: public; Owner: - -- @@ -524,13 +607,6 @@ CREATE INDEX idx1rcgkdd777bpvj0r94sltwd5y ON public."Domain" USING btree (fully_ CREATE INDEX idx5mnf0wn20tno4b9do88j61klr ON public."Domain" USING btree (deletion_time); --- --- Name: idx8ffrqm27qtj20jac056j7yq07; Type: INDEX; Schema: public; Owner: - --- - -CREATE INDEX idx8ffrqm27qtj20jac056j7yq07 ON public."Domain" USING btree (current_sponsor_client_id); - - -- -- Name: idx8nr0ke9mrrx4ewj6pd2ag4rmr; Type: INDEX; Schema: public; Owner: - -- @@ -552,6 +628,27 @@ CREATE INDEX idx_registry_lock_registrar_id ON public."RegistryLock" USING btree CREATE INDEX idx_registry_lock_verification_code ON public."RegistryLock" USING btree (verification_code); +-- +-- Name: idxbn8t4wp85fgxjl8q4ctlscx55; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idxbn8t4wp85fgxjl8q4ctlscx55 ON public."Contact" USING btree (current_sponsor_client_id); + + +-- +-- Name: idxkjt9yaq92876dstimd93hwckh; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idxkjt9yaq92876dstimd93hwckh ON public."Domain" USING btree (current_sponsor_client_id); + + +-- +-- Name: idxn1f711wicdnooa2mqb7g1m55o; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idxn1f711wicdnooa2mqb7g1m55o ON public."Contact" USING btree (deletion_time); + + -- -- Name: idxrwl38wwkli1j7gkvtywi9jokq; Type: INDEX; Schema: public; Owner: - -- @@ -594,6 +691,22 @@ CREATE INDEX registrarpoc_gae_user_id_idx ON public."RegistrarPoc" USING btree ( CREATE INDEX reservedlist_name_idx ON public."ReservedList" USING btree (name); +-- +-- Name: Contact fk1sfyj7o7954prbn1exk7lpnoe; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public."Contact" + ADD CONSTRAINT fk1sfyj7o7954prbn1exk7lpnoe FOREIGN KEY (creation_client_id) REFERENCES public."Registrar"(client_id); + + +-- +-- Name: Domain fk2jc69qyg2tv9hhnmif6oa1cx1; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public."Domain" + ADD CONSTRAINT fk2jc69qyg2tv9hhnmif6oa1cx1 FOREIGN KEY (creation_client_id) REFERENCES public."Registrar"(client_id); + + -- -- Name: RegistryLock fk2lhcwpxlnqijr96irylrh1707; Type: FK CONSTRAINT; Schema: public; Owner: - -- @@ -602,6 +715,14 @@ ALTER TABLE ONLY public."RegistryLock" ADD CONSTRAINT fk2lhcwpxlnqijr96irylrh1707 FOREIGN KEY (relock_revision_id) REFERENCES public."RegistryLock"(revision_id); +-- +-- Name: Domain fk2u3srsfbei272093m3b3xwj23; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public."Domain" + ADD CONSTRAINT fk2u3srsfbei272093m3b3xwj23 FOREIGN KEY (current_sponsor_client_id) REFERENCES public."Registrar"(client_id); + + -- -- Name: ClaimsEntry fk6sc6at5hedffc0nhdcab6ivuq; Type: FK CONSTRAINT; Schema: public; Owner: - -- @@ -618,6 +739,14 @@ ALTER TABLE ONLY public."HostResource_inetAddresses" ADD CONSTRAINT fk6unwhfkcu3oq6q347fxvpagv FOREIGN KEY (host_resource_repo_id) REFERENCES public."HostResource"(repo_id); +-- +-- Name: Contact fk93c185fx7chn68uv7nl6uv2s0; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public."Contact" + ADD CONSTRAINT fk93c185fx7chn68uv7nl6uv2s0 FOREIGN KEY (current_sponsor_client_id) REFERENCES public."Registrar"(client_id); + + -- -- Name: DomainHost fk_domainhost_host_valid; Type: FK CONSTRAINT; Schema: public; Owner: - -- @@ -642,6 +771,22 @@ ALTER TABLE ONLY public."ReservedEntry" ADD CONSTRAINT fkgq03rk0bt1hb915dnyvd3vnfc FOREIGN KEY (revision_id) REFERENCES public."ReservedList"(revision_id); +-- +-- Name: Domain fkjc0r9r5y1lfbt4gpbqw4wsuvq; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public."Domain" + ADD CONSTRAINT fkjc0r9r5y1lfbt4gpbqw4wsuvq FOREIGN KEY (last_epp_update_client_id) REFERENCES public."Registrar"(client_id); + + +-- +-- Name: Contact fkmb7tdiv85863134w1wogtxrb2; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public."Contact" + ADD CONSTRAINT fkmb7tdiv85863134w1wogtxrb2 FOREIGN KEY (last_epp_update_client_id) REFERENCES public."Registrar"(client_id); + + -- -- Name: PremiumEntry fko0gw90lpo1tuee56l0nb6y6g5; Type: FK CONSTRAINT; Schema: public; Owner: - --