diff --git a/core/src/test/java/google/registry/model/OteStatsTestHelper.java b/core/src/test/java/google/registry/model/OteStatsTestHelper.java index 892ceb794..01c8099c0 100644 --- a/core/src/test/java/google/registry/model/OteStatsTestHelper.java +++ b/core/src/test/java/google/registry/model/OteStatsTestHelper.java @@ -14,17 +14,19 @@ package google.registry.model; +import static google.registry.persistence.transaction.TransactionManagerFactory.tm; import static google.registry.testing.DatabaseHelper.createTld; import static google.registry.testing.DatabaseHelper.persistActiveDomain; import static google.registry.testing.DatabaseHelper.persistActiveHost; -import static google.registry.testing.DatabaseHelper.persistDeletedDomain; import static google.registry.testing.DatabaseHelper.persistDeletedHost; +import static google.registry.testing.DatabaseHelper.persistDomainAsDeleted; import static google.registry.testing.DatabaseHelper.persistPremiumList; import static google.registry.testing.DatabaseHelper.persistResource; import static google.registry.testing.TestDataHelper.loadBytes; import static google.registry.util.DateTimeUtils.END_OF_TIME; import static org.joda.money.CurrencyUnit.USD; +import google.registry.model.domain.Domain; import google.registry.model.domain.DomainHistory; import google.registry.model.eppcommon.Trid; import google.registry.model.host.HostHistory; @@ -49,7 +51,7 @@ public final class OteStatsTestHelper { .build()); persistResource( new DomainHistory.Builder() - .setDomain(persistActiveDomain("example.tld")) + .setDomain(persistActiveDomain("restored.tld")) .setRegistrarId(oteAccount1) .setType(Type.DOMAIN_RESTORE) .setXmlBytes(getBytes("domain_restore.xml")) @@ -84,7 +86,7 @@ public final class OteStatsTestHelper { DateTime now = DateTime.now(DateTimeZone.UTC); persistResource( new DomainHistory.Builder() - .setDomain(persistActiveDomain("exampleone.tld")) + .setDomain(loadOrCreateDomain("exampleone.tld")) .setRegistrarId(oteAccount1) .setType(Type.DOMAIN_CREATE) .setXmlBytes(getBytes("domain_create_sunrise.xml")) @@ -92,15 +94,16 @@ public final class OteStatsTestHelper { .build()); persistResource( new DomainHistory.Builder() - .setDomain(persistActiveDomain("example-one.tld")) + .setDomain(loadOrCreateDomain("example-one.tld")) .setRegistrarId(oteAccount1) .setType(Type.DOMAIN_CREATE) .setXmlBytes(getBytes("domain_create_claim_notice.xml")) .setModificationTime(now) .build()); + Domain exampleDomain = loadOrCreateDomain("example.tld"); persistResource( new DomainHistory.Builder() - .setDomain(persistActiveDomain("example.tld")) + .setDomain(exampleDomain) .setRegistrarId(oteAccount1) .setType(Type.DOMAIN_CREATE) .setXmlBytes(getBytes("domain_create_anchor_tenant_fee_standard.xml")) @@ -108,7 +111,7 @@ public final class OteStatsTestHelper { .build()); persistResource( new DomainHistory.Builder() - .setDomain(persistActiveDomain("example.tld")) + .setDomain(exampleDomain) .setRegistrarId(oteAccount1) .setType(Type.DOMAIN_CREATE) .setXmlBytes(getBytes("domain_create_dsdata.xml")) @@ -116,7 +119,7 @@ public final class OteStatsTestHelper { .build()); persistResource( new DomainHistory.Builder() - .setDomain(persistDeletedDomain("example.tld", now)) + .setDomain(persistDomainAsDeleted(loadOrCreateDomain("deleted.tld"), now)) .setRegistrarId(oteAccount1) .setType(Type.DOMAIN_DELETE) .setXmlBytes(getBytes("domain_delete.xml")) @@ -124,7 +127,7 @@ public final class OteStatsTestHelper { .build()); persistResource( new DomainHistory.Builder() - .setDomain(persistActiveDomain("example.tld")) + .setDomain(exampleDomain) .setRegistrarId(oteAccount1) .setType(Type.DOMAIN_TRANSFER_APPROVE) .setXmlBytes(getBytes("domain_transfer_approve.xml")) @@ -132,7 +135,7 @@ public final class OteStatsTestHelper { .build()); persistResource( new DomainHistory.Builder() - .setDomain(persistActiveDomain("example.tld")) + .setDomain(exampleDomain) .setRegistrarId(oteAccount1) .setType(Type.DOMAIN_TRANSFER_CANCEL) .setXmlBytes(getBytes("domain_transfer_cancel.xml")) @@ -140,7 +143,7 @@ public final class OteStatsTestHelper { .build()); persistResource( new DomainHistory.Builder() - .setDomain(persistActiveDomain("example.tld")) + .setDomain(exampleDomain) .setRegistrarId(oteAccount1) .setType(Type.DOMAIN_TRANSFER_REJECT) .setXmlBytes(getBytes("domain_transfer_reject.xml")) @@ -148,7 +151,7 @@ public final class OteStatsTestHelper { .build()); persistResource( new DomainHistory.Builder() - .setDomain(persistActiveDomain("example.tld")) + .setDomain(exampleDomain) .setRegistrarId(oteAccount1) .setType(Type.DOMAIN_TRANSFER_REQUEST) .setXmlBytes(getBytes("domain_transfer_request.xml")) @@ -156,7 +159,7 @@ public final class OteStatsTestHelper { .build()); persistResource( new DomainHistory.Builder() - .setDomain(persistActiveDomain("example.tld")) + .setDomain(exampleDomain) .setRegistrarId(oteAccount1) .setType(Type.DOMAIN_UPDATE) .setXmlBytes(getBytes("domain_update_with_secdns.xml")) @@ -189,4 +192,12 @@ public final class OteStatsTestHelper { private static byte[] getBytes(String filename) throws IOException { return loadBytes(OteStatsTestHelper.class, filename).read(); } + + private static Domain loadOrCreateDomain(String domainName) { + return tm().transact( + () -> + EppResourceUtils.loadByForeignKey( + Domain.class, domainName, tm().getTransactionTime())) + .orElseGet(() -> persistActiveDomain(domainName)); + } } diff --git a/core/src/test/java/google/registry/rdap/RdapJsonFormatterTest.java b/core/src/test/java/google/registry/rdap/RdapJsonFormatterTest.java index eab8ee7b8..f4670d8b6 100644 --- a/core/src/test/java/google/registry/rdap/RdapJsonFormatterTest.java +++ b/core/src/test/java/google/registry/rdap/RdapJsonFormatterTest.java @@ -112,6 +112,11 @@ class RdapJsonFormatterTest { hostNotLinked = makeAndPersistHost( "ns5.cat.みんな", null, null, clock.nowUtc().minusYears(5), "unicoderegistrar"); + // Create an unused domain that references hostBoth and hostNoAddresses so that + // they will have "associated" (ie, StatusValue.LINKED) status. + Domain dog = + persistResource( + makeDomain("dog.みんな", null, null, null, hostBoth, hostNoAddresses, registrar)); hostSuperordinatePendingTransfer = persistResource( makeAndPersistHost( @@ -119,8 +124,7 @@ class RdapJsonFormatterTest { .asBuilder() .setSuperordinateDomain( persistResource( - makeDomain("dog.みんな", null, null, null, null, null, registrar) - .asBuilder() + dog.asBuilder() .addStatusValue(StatusValue.PENDING_TRANSFER) .setTransferData( new DomainTransferData.Builder() @@ -150,9 +154,6 @@ class RdapJsonFormatterTest { .setCreationTimeForTest(clock.nowUtc()) .setLastEppUpdateTime(null) .build()); - // Create an unused domain that references hostBoth and hostNoAddresses so that - // they will have "associated" (ie, StatusValue.LINKED) status. - persistResource(makeDomain("dog.みんな", null, null, null, hostBoth, hostNoAddresses, registrar)); // history entries // We create 3 "transfer approved" entries, to make sure we only save the last one diff --git a/core/src/test/java/google/registry/reporting/spec11/Spec11EmailUtilsTest.java b/core/src/test/java/google/registry/reporting/spec11/Spec11EmailUtilsTest.java index 4c27bfb97..7546ead7d 100644 --- a/core/src/test/java/google/registry/reporting/spec11/Spec11EmailUtilsTest.java +++ b/core/src/test/java/google/registry/reporting/spec11/Spec11EmailUtilsTest.java @@ -253,7 +253,7 @@ class Spec11EmailUtilsTest { // Create an inactive domain and an active domain with the same name. persistResource(loadByEntity(aDomain).asBuilder().addStatusValue(SERVER_HOLD).build()); Host host = persistActiveHost("ns1.example.com"); - aDomain = persistDomainWithHost("a.com", host); + aDomain = persistResource(aDomain.asBuilder().setNameservers(host.createVKey()).build()); emailUtils.emailSpec11Reports( date, diff --git a/core/src/test/java/google/registry/tools/DeleteAllocationTokensCommandTest.java b/core/src/test/java/google/registry/tools/DeleteAllocationTokensCommandTest.java index 2412262c1..66f85310d 100644 --- a/core/src/test/java/google/registry/tools/DeleteAllocationTokensCommandTest.java +++ b/core/src/test/java/google/registry/tools/DeleteAllocationTokensCommandTest.java @@ -43,10 +43,14 @@ class DeleteAllocationTokensCommandTest extends CommandTestCase generated on - 2025-10-10 17:24:50 + 2025-10-23 21:01:05 last flyway file - V213__graceperiodhistory_history_revision_id_hash.sql + V214__domain_unique_domain_name_active.sql @@ -273,7 +273,7 @@ td.section {

 

- SchemaCrawler_Diagram generated by SchemaCrawler 16.27.1 generated on 2025-10-10 17:24:50 + SchemaCrawler_Diagram generated by SchemaCrawler 16.27.1 generated on 2025-10-23 21:01:05 allocationtoken_a08ccbef public."AllocationToken" [table] token text not null domain_name text redemption_domain_repo_id text token_type text diff --git a/db/src/main/resources/sql/er_diagram/full_er_diagram.html b/db/src/main/resources/sql/er_diagram/full_er_diagram.html index 26bbe8b8d..a08e3a24a 100644 --- a/db/src/main/resources/sql/er_diagram/full_er_diagram.html +++ b/db/src/main/resources/sql/er_diagram/full_er_diagram.html @@ -261,11 +261,11 @@ td.section {
generated on - 2025-10-10 17:24:47 + 2025-10-23 21:00:55
last flyway file - V213__graceperiodhistory_history_revision_id_hash.sql + V214__domain_unique_domain_name_active.sql
@@ -273,7 +273,7 @@ td.section {

 

- SchemaCrawler_Diagram generated by SchemaCrawler 16.27.1 generated on 2025-10-10 17:24:47 + SchemaCrawler_Diagram generated by SchemaCrawler 16.27.1 generated on 2025-10-23 21:00:55 allocationtoken_a08ccbef public."AllocationToken" [table] token text not null update_timestamp timestamptz allowed_registrar_ids _text allowed_tlds _text creation_time timestamptz not null discount_fraction float8(17, 17) not null discount_premiums bool not null discount_years int4 not null domain_name text redemption_domain_repo_id text token_status_transitions hstore token_type text redemption_domain_history_id int8 renewal_price_behavior text not null registration_behavior text not null allowed_epp_actions _text renewal_price_amount numeric(19, 2) renewal_price_currency text discount_price_amount numeric(19, 2) discount_price_currency text @@ -4659,6 +4659,18 @@ td.section {
+
+ domain_no_duplicate_active + [unique index] +
+
+ + domain_name + ascending +
+
+ +
domain_domain_name_hash [non-unique hashed index] diff --git a/db/src/main/resources/sql/flyway.txt b/db/src/main/resources/sql/flyway.txt index 51dc75e28..448649cc8 100644 --- a/db/src/main/resources/sql/flyway.txt +++ b/db/src/main/resources/sql/flyway.txt @@ -211,3 +211,4 @@ V210__allocationtoken_token_hash.sql V211__domainhistoryhost_history_revision_id_hash.sql V212__domaindsdatahistory_history_revision_id_hash.sql V213__graceperiodhistory_history_revision_id_hash.sql +V214__domain_unique_domain_name_active.sql diff --git a/db/src/main/resources/sql/flyway/V214__domain_unique_domain_name_active.sql b/db/src/main/resources/sql/flyway/V214__domain_unique_domain_name_active.sql new file mode 100644 index 000000000..e243ddbd6 --- /dev/null +++ b/db/src/main/resources/sql/flyway/V214__domain_unique_domain_name_active.sql @@ -0,0 +1,16 @@ +-- Copyright 2025 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 UNIQUE INDEX CONCURRENTLY IF NOT EXISTS domain_no_duplicate_active ON "Domain" + (domain_name) WHERE (deletion_time = '294247-01-10T04:00:54.775Z'); diff --git a/db/src/main/resources/sql/schema/nomulus.golden.sql b/db/src/main/resources/sql/schema/nomulus.golden.sql index 619421c56..67bb031fc 100644 --- a/db/src/main/resources/sql/schema/nomulus.golden.sql +++ b/db/src/main/resources/sql/schema/nomulus.golden.sql @@ -1987,6 +1987,13 @@ CREATE INDEX domain_history_to_ds_data_history_idx ON public."DomainDsDataHistor CREATE INDEX domain_history_to_transaction_record_idx ON public."DomainTransactionRecord" USING btree (domain_repo_id, history_revision_id); +-- +-- Name: domain_no_duplicate_active; Type: INDEX; Schema: public; Owner: - +-- + +CREATE UNIQUE INDEX domain_no_duplicate_active ON public."Domain" USING btree (domain_name) WHERE (deletion_time = '294247-01-10 04:00:54.775+00'::timestamp with time zone); + + -- -- Name: domaindsdatahistory_domain_history_revision_id_hash; Type: INDEX; Schema: public; Owner: - --