mirror of
https://github.com/google/nomulus
synced 2026-05-20 14:51:48 +00:00
Add unique index for not-deleted domain names (#2853)
This is a backstop against multiple domain creations for the same domain name getting through
This commit is contained in:
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -43,10 +43,14 @@ class DeleteAllocationTokensCommandTest extends CommandTestCase<DeleteAllocation
|
||||
private AllocationToken preNot2;
|
||||
private AllocationToken othrRed;
|
||||
private AllocationToken othrNot;
|
||||
private Domain exampleDomain;
|
||||
private Domain foobarDomain;
|
||||
|
||||
@BeforeEach
|
||||
void beforeEach() {
|
||||
createTlds("foo", "bar");
|
||||
exampleDomain = persistActiveDomain("example.foo");
|
||||
foobarDomain = persistActiveDomain("foo.bar");
|
||||
preRed1 = persistToken("prefix12345AA", null, true);
|
||||
preRed2 = persistToken("prefixgh8907a", null, true);
|
||||
preNot1 = persistToken("prefix2978204", null, false);
|
||||
@@ -97,8 +101,8 @@ class DeleteAllocationTokensCommandTest extends CommandTestCase<DeleteAllocation
|
||||
|
||||
@Test
|
||||
void test_defaultOptions_doesntDeletePerDomainTokens() throws Exception {
|
||||
AllocationToken preDom1 = persistToken("prefixasdfg897as", "foo.bar", false);
|
||||
AllocationToken preDom2 = persistToken("prefix98HAZXadbn", "foo.bar", true);
|
||||
AllocationToken preDom1 = persistToken("prefixasdfg897as", foobarDomain, false);
|
||||
AllocationToken preDom2 = persistToken("prefix98HAZXadbn", foobarDomain, true);
|
||||
runCommandForced("--prefix", "prefix");
|
||||
assertNonexistent(preNot1, preNot2);
|
||||
assertThat(reloadTokens(preRed1, preRed2, preDom1, preDom2, othrRed, othrNot))
|
||||
@@ -107,8 +111,8 @@ class DeleteAllocationTokensCommandTest extends CommandTestCase<DeleteAllocation
|
||||
|
||||
@Test
|
||||
void test_withDomains_doesDeletePerDomainTokens() throws Exception {
|
||||
AllocationToken preDom1 = persistToken("prefixasdfg897as", "foo.bar", false);
|
||||
AllocationToken preDom2 = persistToken("prefix98HAZXadbn", "foo.bar", true);
|
||||
AllocationToken preDom1 = persistToken("prefixasdfg897as", foobarDomain, false);
|
||||
AllocationToken preDom2 = persistToken("prefix98HAZXadbn", foobarDomain, true);
|
||||
runCommandForced("--prefix", "prefix", "--with_domains");
|
||||
assertNonexistent(preNot1, preNot2, preDom1);
|
||||
assertThat(reloadTokens(preRed1, preRed2, preDom2, othrRed, othrNot))
|
||||
@@ -164,17 +168,15 @@ class DeleteAllocationTokensCommandTest extends CommandTestCase<DeleteAllocation
|
||||
assertThat(thrown).hasMessageThat().isEqualTo("Provided prefix should not be blank");
|
||||
}
|
||||
|
||||
private static AllocationToken persistToken(
|
||||
String token, @Nullable String domainName, boolean redeemed) {
|
||||
private AllocationToken persistToken(String token, @Nullable Domain domain, boolean redeemed) {
|
||||
AllocationToken.Builder builder =
|
||||
new AllocationToken.Builder()
|
||||
.setToken(token)
|
||||
.setTokenType(SINGLE_USE)
|
||||
.setDomainName(domainName);
|
||||
.setDomainName(domain == null ? null : domain.getDomainName());
|
||||
if (redeemed) {
|
||||
String domainToPersist = domainName != null ? domainName : "example.foo";
|
||||
Domain domain = persistActiveDomain(domainToPersist);
|
||||
HistoryEntryId historyEntryId = new HistoryEntryId(domain.getRepoId(), 1051L);
|
||||
String repoId = domain == null ? exampleDomain.getRepoId() : domain.getRepoId();
|
||||
HistoryEntryId historyEntryId = new HistoryEntryId(repoId, 1051L);
|
||||
builder.setRedemptionHistoryId(historyEntryId);
|
||||
}
|
||||
return persistResource(builder.build());
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"objectClassName" : "nameserver",
|
||||
"handle" : "C-ROID",
|
||||
"handle" : "D-ROID",
|
||||
"ldhName" : "ns1.dog.xn--q9jyb4c",
|
||||
"unicodeName" : "ns1.dog.みんな",
|
||||
"status" : ["active", "pending transfer"],
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 133 KiB After Width: | Height: | Size: 93 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 139 KiB After Width: | Height: | Size: 95 KiB |
@@ -261,11 +261,11 @@ td.section {
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="property_name">generated on</td>
|
||||
<td class="property_value">2025-10-10 17:24:50</td>
|
||||
<td class="property_value">2025-10-23 21:01:05</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="property_name">last flyway file</td>
|
||||
<td id="lastFlywayFile" class="property_value">V213__graceperiodhistory_history_revision_id_hash.sql</td>
|
||||
<td id="lastFlywayFile" class="property_value">V214__domain_unique_domain_name_active.sql</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -273,7 +273,7 @@ td.section {
|
||||
<p> </p>
|
||||
<svg viewBox="0.00 0.00 4903.00 3732.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="erDiagram" style="overflow: hidden; width: 100%; height: 800px">
|
||||
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 3728)">
|
||||
<title>SchemaCrawler_Diagram</title> <polygon fill="white" stroke="transparent" points="-4,4 -4,-3728 4899,-3728 4899,4 -4,4" /> <text text-anchor="start" x="4655" y="-29.8" font-family="Helvetica,sans-Serif" font-size="14.00">generated by</text> <text text-anchor="start" x="4738" y="-29.8" font-family="Helvetica,sans-Serif" font-size="14.00">SchemaCrawler 16.27.1</text> <text text-anchor="start" x="4654" y="-10.8" font-family="Helvetica,sans-Serif" font-size="14.00">generated on</text> <text text-anchor="start" x="4738" y="-10.8" font-family="Helvetica,sans-Serif" font-size="14.00">2025-10-10 17:24:50</text> <polygon fill="none" stroke="#888888" points="4651,-4 4651,-44 4887,-44 4887,-4 4651,-4" /> <!-- allocationtoken_a08ccbef -->
|
||||
<title>SchemaCrawler_Diagram</title> <polygon fill="white" stroke="transparent" points="-4,4 -4,-3728 4899,-3728 4899,4 -4,4" /> <text text-anchor="start" x="4655" y="-29.8" font-family="Helvetica,sans-Serif" font-size="14.00">generated by</text> <text text-anchor="start" x="4738" y="-29.8" font-family="Helvetica,sans-Serif" font-size="14.00">SchemaCrawler 16.27.1</text> <text text-anchor="start" x="4654" y="-10.8" font-family="Helvetica,sans-Serif" font-size="14.00">generated on</text> <text text-anchor="start" x="4738" y="-10.8" font-family="Helvetica,sans-Serif" font-size="14.00">2025-10-23 21:01:05</text> <polygon fill="none" stroke="#888888" points="4651,-4 4651,-44 4887,-44 4887,-4 4651,-4" /> <!-- allocationtoken_a08ccbef -->
|
||||
<g id="node1" class="node">
|
||||
<title>allocationtoken_a08ccbef</title> <polygon fill="#e9c2f2" stroke="transparent" points="481.5,-978 481.5,-997 667.5,-997 667.5,-978 481.5,-978" /> <text text-anchor="start" x="483.5" y="-984.8" font-family="Helvetica,sans-Serif" font-weight="bold" font-style="italic" font-size="14.00">public."AllocationToken"</text> <polygon fill="#e9c2f2" stroke="transparent" points="667.5,-978 667.5,-997 741.5,-997 741.5,-978 667.5,-978" /> <text text-anchor="start" x="702.5" y="-983.8" font-family="Helvetica,sans-Serif" font-size="14.00">[table]</text> <text text-anchor="start" x="483.5" y="-965.8" font-family="Helvetica,sans-Serif" font-weight="bold" font-style="italic" font-size="14.00">token</text> <text text-anchor="start" x="661.5" y="-964.8" font-family="Helvetica,sans-Serif" font-size="14.00"> </text> <text text-anchor="start" x="669.5" y="-964.8" font-family="Helvetica,sans-Serif" font-size="14.00">text not null</text> <text text-anchor="start" x="483.5" y="-945.8" font-family="Helvetica,sans-Serif" font-size="14.00">domain_name</text> <text text-anchor="start" x="661.5" y="-945.8" font-family="Helvetica,sans-Serif" font-size="14.00"> </text> <text text-anchor="start" x="669.5" y="-945.8" font-family="Helvetica,sans-Serif" font-size="14.00">text</text> <text text-anchor="start" x="483.5" y="-926.8" font-family="Helvetica,sans-Serif" font-size="14.00">redemption_domain_repo_id</text> <text text-anchor="start" x="661.5" y="-926.8" font-family="Helvetica,sans-Serif" font-size="14.00"> </text> <text text-anchor="start" x="669.5" y="-926.8" font-family="Helvetica,sans-Serif" font-size="14.00">text</text> <text text-anchor="start" x="483.5" y="-907.8" font-family="Helvetica,sans-Serif" font-size="14.00">token_type</text> <text text-anchor="start" x="661.5" y="-907.8" font-family="Helvetica,sans-Serif" font-size="14.00"> </text> <text text-anchor="start" x="669.5" y="-907.8" font-family="Helvetica,sans-Serif" font-size="14.00">text</text> <polygon fill="none" stroke="#888888" points="480.5,-901.5 480.5,-998.5 742.5,-998.5 742.5,-901.5 480.5,-901.5" />
|
||||
</g>
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -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
|
||||
|
||||
@@ -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');
|
||||
@@ -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: -
|
||||
--
|
||||
|
||||
Reference in New Issue
Block a user