diff --git a/core/src/main/java/google/registry/model/registry/Registry.java b/core/src/main/java/google/registry/model/registry/Registry.java index 1fab709c1..bef302f52 100644 --- a/core/src/main/java/google/registry/model/registry/Registry.java +++ b/core/src/main/java/google/registry/model/registry/Registry.java @@ -791,6 +791,12 @@ public class Registry extends ImmutableObject implements Buildable { return this; } + @VisibleForTesting + public Builder setPremiumListKey(@Nullable Key premiumList) { + getInstance().premiumList = premiumList; + return this; + } + public Builder setRestoreBillingCost(Money amount) { checkArgument(amount.isPositiveOrZero(), "restoreBillingCost cannot be negative"); getInstance().restoreBillingCost = amount; diff --git a/core/src/main/java/google/registry/model/registry/label/PremiumListUtils.java b/core/src/main/java/google/registry/model/registry/label/PremiumListUtils.java index 3faeda30e..8f4a517b4 100644 --- a/core/src/main/java/google/registry/model/registry/label/PremiumListUtils.java +++ b/core/src/main/java/google/registry/model/registry/label/PremiumListUtils.java @@ -36,11 +36,13 @@ import com.google.common.cache.CacheLoader.InvalidCacheLoadException; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Streams; +import com.google.common.flogger.FluentLogger; import com.googlecode.objectify.Key; import google.registry.model.registry.Registry; import google.registry.model.registry.label.DomainLabelMetrics.PremiumListCheckOutcome; import google.registry.model.registry.label.PremiumList.PremiumListEntry; import google.registry.model.registry.label.PremiumList.PremiumListRevision; +import google.registry.schema.tld.PremiumListDao; import java.util.List; import java.util.Objects; import java.util.Optional; @@ -52,7 +54,9 @@ import org.joda.time.DateTime; public final class PremiumListUtils { /** The number of premium list entry entities that are created and deleted per batch. */ - static final int TRANSACTION_BATCH_SIZE = 200; + private static final int TRANSACTION_BATCH_SIZE = 200; + + private static final FluentLogger logger = FluentLogger.forEnclosingClass(); /** Value type class used by {@link #checkStatus} to return the results of a premiumness check. */ @AutoValue @@ -97,6 +101,19 @@ public final class PremiumListUtils { listName, checkResults.checkOutcome(), DateTime.now(UTC).getMillis() - startTime.getMillis()); + + // Also load the value from Cloud SQL, compare the two results, and log if different. + try { + Optional priceFromSql = PremiumListDao.getPremiumPrice(label, registry); + if (!priceFromSql.equals(checkResults.premiumPrice())) { + logger.atWarning().log( + "Unequal prices for domain %s.%s from Datastore (%s) and Cloud SQL (%s).", + label, registry.getTldStr(), checkResults.premiumPrice(), priceFromSql); + } + } catch (Throwable t) { + logger.atSevere().withCause(t).log( + "Error loading price of domain %s.%s from Cloud SQL.", label, registry.getTldStr()); + } return checkResults.premiumPrice(); } diff --git a/core/src/main/java/google/registry/schema/tld/PremiumEntry.java b/core/src/main/java/google/registry/schema/tld/PremiumEntry.java new file mode 100644 index 000000000..2e50a7a27 --- /dev/null +++ b/core/src/main/java/google/registry/schema/tld/PremiumEntry.java @@ -0,0 +1,43 @@ +// Copyright 2019 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.schema.tld; + +import java.io.Serializable; +import java.math.BigDecimal; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; + +/** + * Entity class for the premium price of an individual domain label. + * + *

These are not persisted directly, but rather, using {@link PremiumList#getLabelsToPrices()}. + */ +@Entity +public class PremiumEntry implements Serializable { + + @Id + @Column(nullable = false) + Long revisionId; + + @Column(nullable = false) + BigDecimal price; + + @Id + @Column(nullable = false) + String domainLabel; + + private PremiumEntry() {} +} diff --git a/core/src/main/java/google/registry/schema/tld/PremiumList.java b/core/src/main/java/google/registry/schema/tld/PremiumList.java index ad31ee75c..595da5e87 100644 --- a/core/src/main/java/google/registry/schema/tld/PremiumList.java +++ b/core/src/main/java/google/registry/schema/tld/PremiumList.java @@ -23,6 +23,7 @@ import com.google.common.hash.BloomFilter; import google.registry.model.CreateAutoTimestamp; import java.math.BigDecimal; import java.util.Map; +import javax.annotation.Nullable; import javax.persistence.CollectionTable; import javax.persistence.Column; import javax.persistence.ElementCollection; @@ -34,6 +35,7 @@ import javax.persistence.Index; import javax.persistence.JoinColumn; import javax.persistence.MapKeyColumn; import javax.persistence.Table; +import org.hibernate.LazyInitializationException; import org.joda.money.CurrencyUnit; import org.joda.time.DateTime; @@ -97,10 +99,16 @@ public class PremiumList { return name; } + /** Returns the {@link CurrencyUnit} used for this list. */ + public CurrencyUnit getCurrency() { + return currency; + } + /** Returns the ID of this revision, or throws if null. */ public Long getRevisionId() { checkState( - revisionId != null, "revisionId is null because it is not persisted in the database"); + revisionId != null, + "revisionId is null because this object has not yet been persisted to the DB"); return revisionId; } @@ -109,9 +117,17 @@ public class PremiumList { return creationTimestamp.getTimestamp(); } - /** Returns a {@link Map} of domain labels to prices. */ + /** + * Returns a {@link Map} of domain labels to prices. + * + *

Note that this is lazily loaded and thus will throw a {@link LazyInitializationException} if + * used outside the transaction in which the given entity was loaded. You generally should not be + * using this anyway as it's inefficient to load all of the PremiumEntry rows if you don't need + * them. To check prices, use {@link PremiumListDao#getPremiumPrice} instead. + */ + @Nullable public ImmutableMap getLabelsToPrices() { - return ImmutableMap.copyOf(labelsToPrices); + return labelsToPrices == null ? null : ImmutableMap.copyOf(labelsToPrices); } /** diff --git a/core/src/main/java/google/registry/schema/tld/PremiumListCache.java b/core/src/main/java/google/registry/schema/tld/PremiumListCache.java new file mode 100644 index 000000000..847083761 --- /dev/null +++ b/core/src/main/java/google/registry/schema/tld/PremiumListCache.java @@ -0,0 +1,107 @@ +// Copyright 2019 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.schema.tld; + +import static google.registry.config.RegistryConfig.getDomainLabelListCacheDuration; +import static google.registry.config.RegistryConfig.getSingletonCachePersistDuration; +import static google.registry.config.RegistryConfig.getStaticPremiumListMaxCachedEntries; +import static google.registry.schema.tld.PremiumListDao.getPriceForLabel; +import static java.util.concurrent.TimeUnit.MILLISECONDS; + +import com.google.auto.value.AutoValue; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import google.registry.util.NonFinalForTesting; +import java.math.BigDecimal; +import java.util.Optional; +import org.jetbrains.annotations.NotNull; +import org.joda.time.Duration; + +/** Caching utils for {@link PremiumList}s. */ +class PremiumListCache { + + /** + * In-memory cache for premium lists. + * + *

This is cached for a shorter duration because we need to periodically reload from the DB to + * check if a new revision has been published, and if so, then use that. + */ + @NonFinalForTesting + static LoadingCache> cachePremiumLists = + createCachePremiumLists(getDomainLabelListCacheDuration()); + + @VisibleForTesting + static LoadingCache> createCachePremiumLists( + Duration cachePersistDuration) { + return CacheBuilder.newBuilder() + .expireAfterWrite(cachePersistDuration.getMillis(), MILLISECONDS) + .build( + new CacheLoader>() { + @Override + public Optional load(@NotNull String premiumListName) { + return PremiumListDao.getLatestRevision(premiumListName); + } + }); + } + + /** + * In-memory price cache for for a given premium list revision and domain label. + * + *

Note that premium list revision ids are globally unique, so this cache is specific to a + * given premium list. Premium list entries might not be present, as indicated by the Optional + * wrapper, and we want to cache that as well. + * + *

This is cached for a long duration (essentially indefinitely) because premium list revisions + * are immutable and cannot ever be changed once created, so the cache need not ever expire. + * + *

A maximum size is set here on the cache because it can potentially grow too big to fit in + * memory if there are a large number of distinct premium list entries being queried (both those + * that exist, as well as those that might exist according to the Bloom filter, must be cached). + * The entries judged least likely to be accessed again will be evicted first. + */ + @NonFinalForTesting + static LoadingCache> cachePremiumEntries = + createCachePremiumEntries(getSingletonCachePersistDuration()); + + @VisibleForTesting + static LoadingCache> createCachePremiumEntries( + Duration cachePersistDuration) { + return CacheBuilder.newBuilder() + .expireAfterWrite(cachePersistDuration.getMillis(), MILLISECONDS) + .maximumSize(getStaticPremiumListMaxCachedEntries()) + .build( + new CacheLoader>() { + @Override + public Optional load(RevisionIdAndLabel revisionIdAndLabel) { + return getPriceForLabel(revisionIdAndLabel); + } + }); + } + + @AutoValue + abstract static class RevisionIdAndLabel { + abstract long revisionId(); + + abstract String label(); + + static RevisionIdAndLabel create(long revisionId, String label) { + return new AutoValue_PremiumListCache_RevisionIdAndLabel(revisionId, label); + } + } + + private PremiumListCache() {} +} diff --git a/core/src/main/java/google/registry/schema/tld/PremiumListDao.java b/core/src/main/java/google/registry/schema/tld/PremiumListDao.java index 8392ea76a..f296f2cbc 100644 --- a/core/src/main/java/google/registry/schema/tld/PremiumListDao.java +++ b/core/src/main/java/google/registry/schema/tld/PremiumListDao.java @@ -17,9 +17,37 @@ package google.registry.schema.tld; import static com.google.common.base.Preconditions.checkArgument; import static google.registry.model.transaction.TransactionManagerFactory.jpaTm; +import com.google.common.cache.CacheLoader.InvalidCacheLoadException; +import com.google.common.util.concurrent.UncheckedExecutionException; +import google.registry.model.registry.Registry; +import google.registry.schema.tld.PremiumListCache.RevisionIdAndLabel; +import java.math.BigDecimal; +import java.util.Optional; +import java.util.concurrent.ExecutionException; +import org.joda.money.Money; + /** Data access object class for {@link PremiumList}. */ public class PremiumListDao { + /** + * Returns the premium price for the specified label and registry, or absent if the label is not + * premium. + */ + public static Optional getPremiumPrice(String label, Registry registry) { + // If the registry has no configured premium list, then no labels are premium. + if (registry.getPremiumList() == null) { + return Optional.empty(); + } + String premiumListName = registry.getPremiumList().getName(); + PremiumList premiumList = + getLatestRevisionCached(premiumListName) + .orElseThrow( + () -> + new IllegalStateException( + String.format("Could not load premium list '%s'", premiumListName))); + return getPremiumPriceFromList(label, premiumList); + } + /** Persist a new premium list to Cloud SQL. */ public static void saveNew(PremiumList premiumList) { jpaTm() @@ -27,18 +55,80 @@ public class PremiumListDao { () -> { checkArgument( !checkExists(premiumList.getName()), - "A premium list of this name already exists: %s.", + "Premium list '%s' already exists", premiumList.getName()); jpaTm().getEntityManager().persist(premiumList); }); } + /** Persist a new revision of an existing premium list to Cloud SQL. */ + public static void update(PremiumList premiumList) { + jpaTm() + .transact( + () -> { + checkArgument( + checkExists(premiumList.getName()), + "Can't update non-existent premium list '%s'", + premiumList.getName()); + jpaTm().getEntityManager().persist(premiumList); + }); + } + + /** + * Returns the most recent revision of the PremiumList with the specified name, if it exists. + * + *

Note that this does not load PremiumList.labelsToPrices! If you need to check + * prices, use {@link #getPremiumPrice}. + */ + static Optional getLatestRevision(String premiumListName) { + return jpaTm() + .transact( + () -> + jpaTm() + .getEntityManager() + .createQuery( + "SELECT pl FROM PremiumList pl WHERE pl.name = :name ORDER BY" + + " pl.revisionId DESC", + PremiumList.class) + .setParameter("name", premiumListName) + .setMaxResults(1) + .getResultStream() + .findFirst()); + } + + static Optional getPriceForLabel(RevisionIdAndLabel revisionIdAndLabel) { + return jpaTm() + .transact( + () -> + jpaTm() + .getEntityManager() + .createQuery( + "SELECT pe.price FROM PremiumEntry pe WHERE pe.revisionId = :revisionId" + + " AND pe.domainLabel = :label", + BigDecimal.class) + .setParameter("revisionId", revisionIdAndLabel.revisionId()) + .setParameter("label", revisionIdAndLabel.label()) + .setMaxResults(1) + .getResultStream() + .findFirst()); + } + + /** Returns the most recent revision of the PremiumList with the specified name, from cache. */ + static Optional getLatestRevisionCached(String premiumListName) { + try { + return PremiumListCache.cachePremiumLists.get(premiumListName); + } catch (ExecutionException e) { + throw new UncheckedExecutionException( + "Could not retrieve premium list named " + premiumListName, e); + } + } + /** * Returns whether the premium list of the given name exists. * *

This means that at least one premium list revision must exist for the given name. */ - public static boolean checkExists(String premiumListName) { + static boolean checkExists(String premiumListName) { return jpaTm() .transact( () -> @@ -52,5 +142,24 @@ public class PremiumListDao { > 0); } + private static Optional getPremiumPriceFromList(String label, PremiumList premiumList) { + // Consult the bloom filter and immediately return if the label definitely isn't premium. + if (!premiumList.getBloomFilter().mightContain(label)) { + return Optional.empty(); + } + RevisionIdAndLabel revisionIdAndLabel = + RevisionIdAndLabel.create(premiumList.getRevisionId(), label); + try { + Optional price = PremiumListCache.cachePremiumEntries.get(revisionIdAndLabel); + return price.map(p -> Money.of(premiumList.getCurrency(), p)); + } catch (InvalidCacheLoadException | ExecutionException e) { + throw new RuntimeException( + String.format( + "Could not load premium entry %s for list %s", + revisionIdAndLabel, premiumList.getName()), + e); + } + } + private PremiumListDao() {} } diff --git a/core/src/main/resources/META-INF/persistence.xml b/core/src/main/resources/META-INF/persistence.xml index c6a1721e4..279f4c776 100644 --- a/core/src/main/resources/META-INF/persistence.xml +++ b/core/src/main/resources/META-INF/persistence.xml @@ -25,6 +25,7 @@ google.registry.schema.cursor.Cursor google.registry.model.transfer.BaseTransferObject google.registry.schema.tld.PremiumList + google.registry.schema.tld.PremiumEntry google.registry.schema.tld.ReservedList google.registry.model.domain.secdns.DelegationSignerData google.registry.model.domain.DesignatedContact diff --git a/core/src/test/java/google/registry/schema/tld/PremiumListDaoTest.java b/core/src/test/java/google/registry/schema/tld/PremiumListDaoTest.java index 1b37ad68e..9b7f8e6df 100644 --- a/core/src/test/java/google/registry/schema/tld/PremiumListDaoTest.java +++ b/core/src/test/java/google/registry/schema/tld/PremiumListDaoTest.java @@ -15,13 +15,25 @@ package google.registry.schema.tld; import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth8.assertThat; +import static google.registry.model.common.EntityGroupRoot.getCrossTldKey; import static google.registry.model.transaction.TransactionManagerFactory.jpaTm; +import static google.registry.testing.DatastoreHelper.createTld; +import static google.registry.testing.DatastoreHelper.newRegistry; +import static google.registry.testing.DatastoreHelper.persistResource; import static google.registry.testing.JUnitBackports.assertThrows; +import static org.joda.money.CurrencyUnit.JPY; +import static org.joda.money.CurrencyUnit.USD; import com.google.common.collect.ImmutableMap; +import com.googlecode.objectify.Key; +import google.registry.model.registry.Registry; import google.registry.model.transaction.JpaTransactionManagerRule; +import google.registry.testing.AppEngineRule; import java.math.BigDecimal; -import org.joda.money.CurrencyUnit; +import java.util.List; +import java.util.Optional; +import org.joda.money.Money; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -35,6 +47,8 @@ public class PremiumListDaoTest { public final JpaTransactionManagerRule jpaTmRule = new JpaTransactionManagerRule.Builder().build(); + @Rule public final AppEngineRule appEngine = AppEngineRule.builder().withDatastore().build(); + private static final ImmutableMap TEST_PRICES = ImmutableMap.of( "silver", @@ -46,7 +60,7 @@ public class PremiumListDaoTest { @Test public void saveNew_worksSuccessfully() { - PremiumList premiumList = PremiumList.create("testname", CurrencyUnit.USD, TEST_PRICES); + PremiumList premiumList = PremiumList.create("testname", USD, TEST_PRICES); PremiumListDao.saveNew(premiumList); jpaTm() .transact( @@ -64,22 +78,114 @@ public class PremiumListDaoTest { }); } + @Test + public void update_worksSuccessfully() { + PremiumListDao.saveNew( + PremiumList.create( + "testname", USD, ImmutableMap.of("firstversion", BigDecimal.valueOf(123.45)))); + PremiumListDao.update(PremiumList.create("testname", USD, TEST_PRICES)); + jpaTm() + .transact( + () -> { + List persistedLists = + jpaTm() + .getEntityManager() + .createQuery( + "SELECT pl FROM PremiumList pl WHERE pl.name = :name ORDER BY" + + " pl.revisionId", + PremiumList.class) + .setParameter("name", "testname") + .getResultList(); + assertThat(persistedLists).hasSize(2); + assertThat(persistedLists.get(1).getLabelsToPrices()) + .containsExactlyEntriesIn(TEST_PRICES); + assertThat(persistedLists.get(1).getCreationTimestamp()) + .isEqualTo(jpaTmRule.getTxnClock().nowUtc()); + }); + } + @Test public void saveNew_throwsWhenPremiumListAlreadyExists() { - PremiumListDao.saveNew(PremiumList.create("testlist", CurrencyUnit.USD, TEST_PRICES)); + PremiumListDao.saveNew(PremiumList.create("testlist", USD, TEST_PRICES)); IllegalArgumentException thrown = assertThrows( IllegalArgumentException.class, - () -> - PremiumListDao.saveNew( - PremiumList.create("testlist", CurrencyUnit.USD, TEST_PRICES))); - assertThat(thrown).hasMessageThat().contains("A premium list of this name already exists"); + () -> PremiumListDao.saveNew(PremiumList.create("testlist", USD, TEST_PRICES))); + assertThat(thrown).hasMessageThat().isEqualTo("Premium list 'testlist' already exists"); + } + + @Test + public void update_throwsWhenPremiumListDoesntExist() { + IllegalArgumentException thrown = + assertThrows( + IllegalArgumentException.class, + () -> PremiumListDao.update(PremiumList.create("testlist", USD, TEST_PRICES))); + assertThat(thrown) + .hasMessageThat() + .isEqualTo("Can't update non-existent premium list 'testlist'"); } @Test public void checkExists_worksSuccessfully() { assertThat(PremiumListDao.checkExists("testlist")).isFalse(); - PremiumListDao.saveNew(PremiumList.create("testlist", CurrencyUnit.USD, TEST_PRICES)); + PremiumListDao.saveNew(PremiumList.create("testlist", USD, TEST_PRICES)); assertThat(PremiumListDao.checkExists("testlist")).isTrue(); } + + @Test + public void getLatestRevision_returnsEmptyForNonexistentList() { + assertThat(PremiumListDao.getLatestRevision("nonexistentlist")).isEmpty(); + } + + @Test + public void getLatestRevision_worksSuccessfully() { + PremiumListDao.saveNew( + PremiumList.create("list1", JPY, ImmutableMap.of("wrong", BigDecimal.valueOf(1000.50)))); + PremiumListDao.update(PremiumList.create("list1", JPY, TEST_PRICES)); + jpaTm() + .transact( + () -> { + Optional persistedList = PremiumListDao.getLatestRevision("list1"); + assertThat(persistedList).isPresent(); + assertThat(persistedList.get().getName()).isEqualTo("list1"); + assertThat(persistedList.get().getCurrency()).isEqualTo(JPY); + assertThat(persistedList.get().getLabelsToPrices()) + .containsExactlyEntriesIn(TEST_PRICES); + }); + } + + @Test + public void getPremiumPrice_returnsNoneWhenNoPremiumListConfigured() { + persistResource(newRegistry("foobar", "FOOBAR").asBuilder().setPremiumList(null).build()); + assertThat(PremiumListDao.getPremiumPrice("rich", Registry.get("foobar"))).isEmpty(); + } + + @Test + public void getPremiumPrice_worksSuccessfully() { + persistResource( + newRegistry("foobar", "FOOBAR") + .asBuilder() + .setPremiumListKey( + Key.create( + getCrossTldKey(), + google.registry.model.registry.label.PremiumList.class, + "premlist")) + .build()); + PremiumListDao.saveNew(PremiumList.create("premlist", USD, TEST_PRICES)); + assertThat(PremiumListDao.getPremiumPrice("silver", Registry.get("foobar"))) + .hasValue(Money.of(USD, 10.23)); + assertThat(PremiumListDao.getPremiumPrice("gold", Registry.get("foobar"))) + .hasValue(Money.of(USD, 1305.47)); + assertThat(PremiumListDao.getPremiumPrice("zirconium", Registry.get("foobar"))).isEmpty(); + } + + @Test + public void testGetPremiumPrice_throwsWhenPremiumListCantBeLoaded() { + createTld("tld"); + IllegalStateException thrown = + assertThrows( + IllegalStateException.class, + () -> PremiumListDao.getPremiumPrice("foobar", Registry.get("tld"))); + assertThat(thrown).hasMessageThat().isEqualTo("Could not load premium list 'tld'"); + } } diff --git a/db/src/main/resources/sql/flyway/V11__premium_entry_reorder_column.sql b/db/src/main/resources/sql/flyway/V11__premium_entry_reorder_column.sql new file mode 100644 index 000000000..85011636d --- /dev/null +++ b/db/src/main/resources/sql/flyway/V11__premium_entry_reorder_column.sql @@ -0,0 +1,16 @@ +-- Copyright 2019 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. + +-- Remove default set in V9 that we no longer need. +alter table "PremiumList" alter column currency drop default; diff --git a/db/src/main/resources/sql/flyway/V9__premium_list_currency_type.sql b/db/src/main/resources/sql/flyway/V9__premium_list_currency_type.sql index 9530ae0e6..73bf97b38 100644 --- a/db/src/main/resources/sql/flyway/V9__premium_list_currency_type.sql +++ b/db/src/main/resources/sql/flyway/V9__premium_list_currency_type.sql @@ -15,5 +15,5 @@ -- Note: We're OK with dropping this data since it's not live in production yet. alter table "PremiumList" drop column if exists currency; --- TODO(mcilwain): Add a subsequent schema change to remove this default. +-- Note: This default was removed in V11. alter table "PremiumList" add column currency text not null default 'USD'; 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 819ef6778..7f990757d 100644 --- a/db/src/main/resources/sql/schema/db-schema.sql.generated +++ b/db/src/main/resources/sql/schema/db-schema.sql.generated @@ -132,8 +132,8 @@ create table "PremiumEntry" ( revision_id int8 not null, - price numeric(19, 2) not null, domain_label text not null, + price numeric(19, 2) not null, primary key (revision_id, domain_label) ); diff --git a/db/src/main/resources/sql/schema/nomulus.golden.sql b/db/src/main/resources/sql/schema/nomulus.golden.sql index e0341b6f7..36fb4bf8c 100644 --- a/db/src/main/resources/sql/schema/nomulus.golden.sql +++ b/db/src/main/resources/sql/schema/nomulus.golden.sql @@ -93,7 +93,7 @@ CREATE TABLE public."PremiumList" ( creation_timestamp timestamp with time zone NOT NULL, name text NOT NULL, bloom_filter bytea NOT NULL, - currency text DEFAULT 'USD'::text NOT NULL + currency text NOT NULL );