diff --git a/core/src/main/java/google/registry/bsa/BlockList.java b/core/src/main/java/google/registry/bsa/BlockList.java new file mode 100644 index 000000000..cde6ec582 --- /dev/null +++ b/core/src/main/java/google/registry/bsa/BlockList.java @@ -0,0 +1,21 @@ +// Copyright 2023 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.bsa; + +/** Identifiers of the BSA lists with blocking labels. */ +public enum BlockList { + BLOCK, + BLOCK_PLUS; +} diff --git a/core/src/main/java/google/registry/bsa/DownloadStage.java b/core/src/main/java/google/registry/bsa/DownloadStage.java new file mode 100644 index 000000000..7e196b0de --- /dev/null +++ b/core/src/main/java/google/registry/bsa/DownloadStage.java @@ -0,0 +1,20 @@ +// Copyright 2023 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.bsa; + +/** The processing stages of a download. */ +public enum DownloadStage { + DOWNLOAD; +} diff --git a/core/src/main/java/google/registry/bsa/persistence/BsaDomainInUse.java b/core/src/main/java/google/registry/bsa/persistence/BsaDomainInUse.java new file mode 100644 index 000000000..9ea7526bf --- /dev/null +++ b/core/src/main/java/google/registry/bsa/persistence/BsaDomainInUse.java @@ -0,0 +1,102 @@ +// Copyright 2023 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.bsa.persistence; + +import com.google.common.base.Objects; +import google.registry.bsa.persistence.BsaDomainInUse.BsaDomainInUseId; +import google.registry.model.CreateAutoTimestamp; +import google.registry.persistence.VKey; +import java.io.Serializable; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.Id; +import javax.persistence.IdClass; + +/** A domain matching a BSA label but is in use (registered or reserved), so cannot be blocked. */ +@Entity +@IdClass(BsaDomainInUseId.class) +public class BsaDomainInUse { + @Id String label; + @Id String tld; + + @Column(nullable = false) + @Enumerated(EnumType.STRING) + Reason reason; + + /** + * Creation time of this record, which is the most recent time when the domain was detected to be + * in use wrt BSA. It may be during the processing of a download, or during some other job that + * refreshes the state. + * + *

This field is for information only. + */ + @SuppressWarnings("unused") + @Column(nullable = false) + CreateAutoTimestamp createTime = CreateAutoTimestamp.create(null); + + // For Hibernate + BsaDomainInUse() {} + + public BsaDomainInUse(String label, String tld, Reason reason) { + this.label = label; + this.tld = tld; + this.reason = reason; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof BsaDomainInUse)) { + return false; + } + BsaDomainInUse that = (BsaDomainInUse) o; + return Objects.equal(label, that.label) + && Objects.equal(tld, that.tld) + && reason == that.reason + && Objects.equal(createTime, that.createTime); + } + + @Override + public int hashCode() { + return Objects.hashCode(label, tld, reason, createTime); + } + + enum Reason { + REGISTERED, + RESERVED; + } + + static class BsaDomainInUseId implements Serializable { + + private String label; + private String tld; + + // For Hibernate + BsaDomainInUseId() {} + + BsaDomainInUseId(String label, String tld) { + this.label = label; + this.tld = tld; + } + } + + static VKey vKey(String label, String tld) { + return VKey.create(BsaDomainInUse.class, new BsaDomainInUseId(label, tld)); + } +} diff --git a/core/src/main/java/google/registry/bsa/persistence/BsaDownload.java b/core/src/main/java/google/registry/bsa/persistence/BsaDownload.java new file mode 100644 index 000000000..f2307dce5 --- /dev/null +++ b/core/src/main/java/google/registry/bsa/persistence/BsaDownload.java @@ -0,0 +1,131 @@ +// Copyright 2023 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.bsa.persistence; + +import static com.google.common.collect.ImmutableMap.toImmutableMap; +import static google.registry.bsa.DownloadStage.DOWNLOAD; + +import com.google.common.base.Joiner; +import com.google.common.base.Objects; +import com.google.common.base.Splitter; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSortedMap; +import google.registry.bsa.BlockList; +import google.registry.bsa.DownloadStage; +import google.registry.model.CreateAutoTimestamp; +import google.registry.model.UpdateAutoTimestamp; +import google.registry.persistence.VKey; +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 javax.persistence.Index; +import javax.persistence.Table; +import org.joda.time.DateTime; + +/** Records of ongoing and completed download jobs. */ +@Entity +@Table(indexes = {@Index(columnList = "creationTime")}) +public class BsaDownload { + + private static final Joiner CSV_JOINER = Joiner.on(','); + private static final Splitter CSV_SPLITTER = Splitter.on(','); + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + Long jobId; + + @Column(nullable = false) + CreateAutoTimestamp creationTime = CreateAutoTimestamp.create(null); + + @Column(nullable = false) + UpdateAutoTimestamp updateTime = UpdateAutoTimestamp.create(null); + + @Column(nullable = false) + String blockListChecksums = ""; + + @Column(nullable = false) + @Enumerated(EnumType.STRING) + DownloadStage stage = DOWNLOAD; + + BsaDownload() {} + + public long getJobId() { + return jobId; + } + + /** + * Returns the starting time of this job as a string, which can be used as folder name on GCS when + * storing download data. + */ + public String getJobName() { + return creationTime.getTimestamp().toString(); + } + + public DownloadStage getStage() { + return this.stage; + } + + BsaDownload setStage(DownloadStage stage) { + this.stage = stage; + return this; + } + + DateTime getCreationTime() { + return creationTime.getTimestamp(); + } + + BsaDownload setBlockListChecksums(ImmutableMap checksums) { + blockListChecksums = + CSV_JOINER.withKeyValueSeparator("=").join(ImmutableSortedMap.copyOf(checksums)); + return this; + } + + ImmutableMap getChecksums() { + if (blockListChecksums.isEmpty()) { + return ImmutableMap.of(); + } + return CSV_SPLITTER.withKeyValueSeparator('=').split(blockListChecksums).entrySet().stream() + .collect( + toImmutableMap(entry -> BlockList.valueOf(entry.getKey()), entry -> entry.getValue())); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof BsaDownload)) { + return false; + } + BsaDownload that = (BsaDownload) o; + return Objects.equal(creationTime, that.creationTime) + && Objects.equal(updateTime, that.updateTime) + && Objects.equal(blockListChecksums, that.blockListChecksums) + && stage == that.stage; + } + + @Override + public int hashCode() { + return Objects.hashCode(creationTime, updateTime, blockListChecksums, stage); + } + + static VKey vKey(long jobId) { + return VKey.create(BsaDownload.class, Long.valueOf(jobId)); + } +} diff --git a/core/src/main/java/google/registry/bsa/persistence/BsaLabel.java b/core/src/main/java/google/registry/bsa/persistence/BsaLabel.java new file mode 100644 index 000000000..976e4d8ed --- /dev/null +++ b/core/src/main/java/google/registry/bsa/persistence/BsaLabel.java @@ -0,0 +1,79 @@ +// Copyright 2023 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.bsa.persistence; + +import com.google.common.base.Objects; +import google.registry.persistence.VKey; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import org.joda.time.DateTime; + +/** + * Specifies a second-level TLD name that should be blocked from registration in all TLDs except by + * the label's owner. + * + *

The label is valid (wrt IDN) in at least one TLD. + */ +@Entity +public final class BsaLabel { + + @Id String label; + + /** + * Creation time of this label. This field is for human use, and should give the name of the GCS + * folder that contains the downloaded BSA data. + * + *

See {@link BsaDownload#getCreationTime} and {@link BsaDownload#getJobName} for more + * information. + */ + @SuppressWarnings("unused") + @Column(nullable = false) + DateTime creationTime; + + // For Hibernate. + BsaLabel() {} + + BsaLabel(String label, DateTime creationTime) { + this.label = label; + this.creationTime = creationTime; + } + + /** Returns the label to be blocked. */ + public String getLabel() { + return label; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof BsaLabel)) { + return false; + } + BsaLabel label1 = (BsaLabel) o; + return Objects.equal(label, label1.label) && Objects.equal(creationTime, label1.creationTime); + } + + @Override + public int hashCode() { + return Objects.hashCode(label, creationTime); + } + + static VKey vKey(String label) { + return VKey.create(BsaLabel.class, label); + } +} diff --git a/core/src/main/java/google/registry/persistence/transaction/DatabaseException.java b/core/src/main/java/google/registry/persistence/transaction/DatabaseException.java index 695f55203..3219c5c11 100644 --- a/core/src/main/java/google/registry/persistence/transaction/DatabaseException.java +++ b/core/src/main/java/google/registry/persistence/transaction/DatabaseException.java @@ -39,7 +39,7 @@ import javax.persistence.PersistenceException; *

See the {@code logging.properties} files in the {@code env} package for the specific Hibernate * classes that have logging suppressed. */ -class DatabaseException extends PersistenceException { +public class DatabaseException extends PersistenceException { private transient String cachedMessage; diff --git a/core/src/main/resources/META-INF/persistence.xml b/core/src/main/resources/META-INF/persistence.xml index db36beea4..ee3cfd10b 100644 --- a/core/src/main/resources/META-INF/persistence.xml +++ b/core/src/main/resources/META-INF/persistence.xml @@ -38,6 +38,9 @@ META-INF/orm.xml + google.registry.bsa.persistence.BsaDownload + google.registry.bsa.persistence.BsaLabel + google.registry.bsa.persistence.BsaDomainInUse google.registry.model.billing.BillingCancellation google.registry.model.billing.BillingEvent google.registry.model.billing.BillingRecurrence diff --git a/core/src/test/java/google/registry/bsa/persistence/BsaDomainInUseTest.java b/core/src/test/java/google/registry/bsa/persistence/BsaDomainInUseTest.java new file mode 100644 index 000000000..b3a4bfa3d --- /dev/null +++ b/core/src/test/java/google/registry/bsa/persistence/BsaDomainInUseTest.java @@ -0,0 +1,74 @@ +// Copyright 2023 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.bsa.persistence; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth8.assertThat; +import static google.registry.persistence.transaction.TransactionManagerFactory.tm; +import static org.joda.time.DateTimeZone.UTC; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import google.registry.bsa.persistence.BsaDomainInUse.Reason; +import google.registry.persistence.transaction.DatabaseException; +import google.registry.persistence.transaction.JpaTestExtensions; +import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationWithCoverageExtension; +import google.registry.testing.FakeClock; +import org.joda.time.DateTime; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +/** Unit tests for {@link BsaDomainInUse}. */ +public class BsaDomainInUseTest { + + protected FakeClock fakeClock = new FakeClock(DateTime.now(UTC)); + + @RegisterExtension + final JpaIntegrationWithCoverageExtension jpa = + new JpaTestExtensions.Builder().withClock(fakeClock).buildIntegrationWithCoverageExtension(); + + @Test + void persist() { + tm().transact(() -> tm().put(new BsaLabel("label", fakeClock.nowUtc()))); + tm().transact(() -> tm().put(new BsaDomainInUse("label", "tld", Reason.REGISTERED))); + BsaDomainInUse persisted = + tm().transact(() -> tm().loadByKey(BsaDomainInUse.vKey("label", "tld"))); + assertThat(persisted.label).isEqualTo("label"); + assertThat(persisted.tld).isEqualTo("tld"); + assertThat(persisted.reason).isEqualTo(Reason.REGISTERED); + } + + @Test + void cascadeDeletion() { + tm().transact(() -> tm().put(new BsaLabel("label", fakeClock.nowUtc()))); + tm().transact(() -> tm().put(new BsaDomainInUse("label", "tld", Reason.REGISTERED))); + assertThat(tm().transact(() -> tm().loadByKeyIfPresent(BsaDomainInUse.vKey("label", "tld")))) + .isPresent(); + tm().transact(() -> tm().delete(BsaLabel.vKey("label"))); + assertThat(tm().transact(() -> tm().loadByKeyIfPresent(BsaDomainInUse.vKey("label", "tld")))) + .isEmpty(); + } + + @Test + void insertDomainWithoutLabel() { + assertThat( + assertThrows( + DatabaseException.class, + () -> + tm().transact( + () -> tm().put(new BsaDomainInUse("label", "tld", Reason.REGISTERED))))) + .hasMessageThat() + .contains("violates foreign key constraint"); + } +} diff --git a/core/src/test/java/google/registry/bsa/persistence/BsaDownloadTest.java b/core/src/test/java/google/registry/bsa/persistence/BsaDownloadTest.java new file mode 100644 index 000000000..a0acf2982 --- /dev/null +++ b/core/src/test/java/google/registry/bsa/persistence/BsaDownloadTest.java @@ -0,0 +1,66 @@ +// Copyright 2023 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.bsa.persistence; + +import static com.google.common.truth.Truth.assertThat; +import static google.registry.bsa.BlockList.BLOCK; +import static google.registry.bsa.BlockList.BLOCK_PLUS; +import static google.registry.bsa.DownloadStage.DOWNLOAD; +import static google.registry.persistence.transaction.TransactionManagerFactory.tm; +import static org.joda.time.DateTimeZone.UTC; + +import com.google.common.collect.ImmutableMap; +import google.registry.bsa.BlockList; +import google.registry.persistence.transaction.JpaTestExtensions; +import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationWithCoverageExtension; +import google.registry.testing.FakeClock; +import org.joda.time.DateTime; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +/** Unit test for {@link BsaDownload}. */ +public class BsaDownloadTest { + + protected FakeClock fakeClock = new FakeClock(DateTime.now(UTC)); + + @RegisterExtension + final JpaIntegrationWithCoverageExtension jpa = + new JpaTestExtensions.Builder().withClock(fakeClock).buildIntegrationWithCoverageExtension(); + + @Test + void saveJob() { + BsaDownload persisted = tm().transact(() -> tm().getEntityManager().merge(new BsaDownload())); + assertThat(persisted.jobId).isNotNull(); + assertThat(persisted.creationTime.getTimestamp()).isEqualTo(fakeClock.nowUtc()); + assertThat(persisted.stage).isEqualTo(DOWNLOAD); + } + + @Test + void loadJobByKey() { + BsaDownload persisted = tm().transact(() -> tm().getEntityManager().merge(new BsaDownload())); + assertThat(tm().transact(() -> tm().loadByKey(BsaDownload.vKey(persisted.jobId)))) + .isEqualTo(persisted); + } + + @Test + void checksums() { + BsaDownload job = new BsaDownload(); + assertThat(job.getChecksums()).isEmpty(); + ImmutableMap checksums = ImmutableMap.of(BLOCK, "a", BLOCK_PLUS, "b"); + job.setBlockListChecksums(checksums); + assertThat(job.getChecksums()).isEqualTo(checksums); + assertThat(job.blockListChecksums).isEqualTo("BLOCK=a,BLOCK_PLUS=b"); + } +} diff --git a/core/src/test/java/google/registry/bsa/persistence/BsaLabelTest.java b/core/src/test/java/google/registry/bsa/persistence/BsaLabelTest.java new file mode 100644 index 000000000..b49686a63 --- /dev/null +++ b/core/src/test/java/google/registry/bsa/persistence/BsaLabelTest.java @@ -0,0 +1,44 @@ +// Copyright 2023 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.bsa.persistence; + +import static com.google.common.truth.Truth.assertThat; +import static google.registry.persistence.transaction.TransactionManagerFactory.tm; +import static org.joda.time.DateTimeZone.UTC; + +import google.registry.persistence.transaction.JpaTestExtensions; +import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationWithCoverageExtension; +import google.registry.testing.FakeClock; +import org.joda.time.DateTime; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +/** Unit tests for {@link BsaLabel}. */ +public class BsaLabelTest { + + protected FakeClock fakeClock = new FakeClock(DateTime.now(UTC)); + + @RegisterExtension + final JpaIntegrationWithCoverageExtension jpa = + new JpaTestExtensions.Builder().withClock(fakeClock).buildIntegrationWithCoverageExtension(); + + @Test + void persist() { + tm().transact(() -> tm().put(new BsaLabel("label", fakeClock.nowUtc()))); + BsaLabel persisted = tm().transact(() -> tm().loadByKey(BsaLabel.vKey("label"))); + assertThat(persisted.getLabel()).isEqualTo("label"); + assertThat(persisted.creationTime).isEqualTo(fakeClock.nowUtc()); + } +} 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 15d44278e..359e92bf4 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,9 @@ package google.registry.schema.integration; import static com.google.common.truth.Truth.assert_; +import google.registry.bsa.persistence.BsaDomainInUseTest; +import google.registry.bsa.persistence.BsaDownloadTest; +import google.registry.bsa.persistence.BsaLabelTest; import google.registry.model.billing.BillingBaseTest; import google.registry.model.common.CursorTest; import google.registry.model.common.DnsRefreshRequestTest; @@ -82,6 +85,9 @@ import org.junit.runner.RunWith; BeforeSuiteTest.class, AllocationTokenTest.class, BillingBaseTest.class, + BsaDomainInUseTest.class, + BsaDownloadTest.class, + BsaLabelTest.class, BulkPricingPackageTest.class, ClaimsListDaoTest.class, ContactHistoryTest.class, 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 9933c7d90..7c3c24ea8 100644 --- a/db/src/main/resources/sql/schema/db-schema.sql.generated +++ b/db/src/main/resources/sql/schema/db-schema.sql.generated @@ -85,6 +85,29 @@ primary key (billing_recurrence_id) ); + create table "BsaDomainInUse" ( + label text not null, + tld text not null, + creation_time timestamptz not null, + reason text not null, + primary key (label, tld) + ); + + create table "BsaDownload" ( + job_id bigserial not null, + block_list_checksums text not null, + creation_time timestamptz not null, + stage text not null, + update_timestamp timestamptz, + primary key (job_id) + ); + + create table "BsaLabel" ( + label text not null, + creation_time timestamptz not null, + primary key (label) + ); + create table "ClaimsEntry" ( revision_id int8 not null, domain_label text not null, @@ -785,6 +808,7 @@ create index IDXoqttafcywwdn41um6kwlt0n8b on "BillingRecurrence" (domain_repo_id create index IDXp3usbtvk0v1m14i5tdp4xnxgc on "BillingRecurrence" (recurrence_end_time); create index IDXp0pxi708hlu4n40qhbtihge8x on "BillingRecurrence" (recurrence_last_expansion); create index IDXjny8wuot75b5e6p38r47wdawu on "BillingRecurrence" (recurrence_time_of_year); +create index IDXj874kw19bgdnkxo1rue45jwlw on "BsaDownload" (creation_time); create index IDX3y752kr9uh4kh6uig54vemx0l on "Contact" (creation_time); create index IDXtm415d6fe1rr35stm33s5mg18 on "Contact" (current_sponsor_registrar_id); create index IDXn1f711wicdnooa2mqb7g1m55o on "Contact" (deletion_time);