From cf092c7e50f30a6a5e40280436d5ceded7d0b946 Mon Sep 17 00:00:00 2001 From: Shicong Huang Date: Wed, 27 May 2020 15:59:19 -0400 Subject: [PATCH] Generate sql schema for BillingEvent (#565) * Generate sql schema for BillingEvent * Change to use sequence * Address comments * Resolve warnings and remove duplicate cost related fields * Increase the flayway file version to V25 * Remove extra space * Split to 3 tables, merge VKey * Rename talbes * Rename repoId to domainRepoId * Exclude VKey in schema.txt * Rename target_id to domain_name * Fix javadoc * Resolve comments --- .../ExpandRecurringBillingEventsAction.java | 168 +++++----- .../flows/domain/DomainCreateFlow.java | 2 +- .../google/registry/model/ModelUtils.java | 8 +- .../registry/model/billing/BillingEvent.java | 156 ++++++++-- .../registry/model/common/TimeOfYear.java | 8 +- .../model/domain/token/AllocationToken.java | 7 + .../registry/model/ofy/ObjectifyService.java | 15 +- .../BillingEventFlagSetConverter.java | 35 +++ .../main/resources/META-INF/persistence.xml | 6 + ...xpandRecurringBillingEventsActionTest.java | 34 +- .../google/registry/flows/EppTestCase.java | 7 +- .../flows/domain/DomainCreateFlowTest.java | 2 +- .../flows/domain/DomainDeleteFlowTest.java | 2 +- .../domain/DomainTransferApproveFlowTest.java | 4 +- .../domain/DomainTransferRequestFlowTest.java | 7 +- .../model/billing/BillingEventTest.java | 181 ++++++++--- .../transaction/JpaEntityCoverage.java | 2 +- .../integration/SqlIntegrationTestSuite.java | 2 + .../google/registry/model/schema.txt | 16 +- .../sql/flyway/V26__create_billing_event.sql | 104 +++++++ .../sql/schema/db-schema.sql.generated | 60 ++++ .../resources/sql/schema/nomulus.golden.sql | 294 ++++++++++++++++++ 22 files changed, 941 insertions(+), 179 deletions(-) create mode 100644 core/src/main/java/google/registry/persistence/converter/BillingEventFlagSetConverter.java create mode 100644 db/src/main/resources/sql/flyway/V26__create_billing_event.sql diff --git a/core/src/main/java/google/registry/batch/ExpandRecurringBillingEventsAction.java b/core/src/main/java/google/registry/batch/ExpandRecurringBillingEventsAction.java index 906eacef3..40cd37e78 100644 --- a/core/src/main/java/google/registry/batch/ExpandRecurringBillingEventsAction.java +++ b/core/src/main/java/google/registry/batch/ExpandRecurringBillingEventsAction.java @@ -155,89 +155,100 @@ public class ExpandRecurringBillingEventsAction implements Runnable { } int numBillingEventsSaved = 0; try { - numBillingEventsSaved = tm().transactNew(() -> { - ImmutableSet.Builder syntheticOneTimesBuilder = - new ImmutableSet.Builder<>(); - final Registry tld = Registry.get(getTldFromDomainName(recurring.getTargetId())); + numBillingEventsSaved = + tm().transactNew( + () -> { + ImmutableSet.Builder syntheticOneTimesBuilder = + new ImmutableSet.Builder<>(); + final Registry tld = + Registry.get(getTldFromDomainName(recurring.getTargetId())); - // Determine the complete set of times at which this recurring event should occur - // (up to and including the runtime of the mapreduce). - Iterable eventTimes = - recurring.getRecurrenceTimeOfYear().getInstancesInRange(Range.closed( - recurring.getEventTime(), - earliestOf(recurring.getRecurrenceEndTime(), executeTime))); + // Determine the complete set of times at which this recurring event should + // occur (up to and including the runtime of the mapreduce). + Iterable eventTimes = + recurring + .getRecurrenceTimeOfYear() + .getInstancesInRange( + Range.closed( + recurring.getEventTime(), + earliestOf(recurring.getRecurrenceEndTime(), executeTime))); - // Convert these event times to billing times - final ImmutableSet billingTimes = - getBillingTimesInScope(eventTimes, cursorTime, executeTime, tld); + // Convert these event times to billing times + final ImmutableSet billingTimes = + getBillingTimesInScope(eventTimes, cursorTime, executeTime, tld); - Key domainKey = recurring.getParentKey().getParent(); - Iterable oneTimesForDomain = - ofy().load().type(OneTime.class).ancestor(domainKey); + Key domainKey = recurring.getParentKey().getParent(); + Iterable oneTimesForDomain = + ofy().load().type(OneTime.class).ancestor(domainKey); - // Determine the billing times that already have OneTime events persisted. - ImmutableSet existingBillingTimes = - getExistingBillingTimes(oneTimesForDomain, recurring); + // Determine the billing times that already have OneTime events persisted. + ImmutableSet existingBillingTimes = + getExistingBillingTimes(oneTimesForDomain, recurring); - ImmutableSet.Builder historyEntriesBuilder = - new ImmutableSet.Builder<>(); - // Create synthetic OneTime events for all billing times that do not yet have an event - // persisted. - for (DateTime billingTime : difference(billingTimes, existingBillingTimes)) { - // Construct a new HistoryEntry that parents over the OneTime - HistoryEntry historyEntry = new HistoryEntry.Builder() - .setBySuperuser(false) - .setClientId(recurring.getClientId()) - .setModificationTime(tm().getTransactionTime()) - .setParent(domainKey) - .setPeriod(Period.create(1, YEARS)) - .setReason("Domain autorenewal by ExpandRecurringBillingEventsAction") - .setRequestedByRegistrar(false) - .setType(DOMAIN_AUTORENEW) - // Don't write a domain transaction record if the recurrence was ended prior to the - // billing time (i.e. a domain was deleted during the autorenew grace period). - .setDomainTransactionRecords( - recurring.getRecurrenceEndTime().isBefore(billingTime) - ? ImmutableSet.of() - : ImmutableSet.of( - DomainTransactionRecord.create( - tld.getTldStr(), - // We report this when the autorenew grace period ends - billingTime, - TransactionReportField.netRenewsFieldFromYears(1), - 1))) - .build(); - historyEntriesBuilder.add(historyEntry); + ImmutableSet.Builder historyEntriesBuilder = + new ImmutableSet.Builder<>(); + // Create synthetic OneTime events for all billing times that do not yet have + // an event persisted. + for (DateTime billingTime : difference(billingTimes, existingBillingTimes)) { + // Construct a new HistoryEntry that parents over the OneTime + HistoryEntry historyEntry = + new HistoryEntry.Builder() + .setBySuperuser(false) + .setClientId(recurring.getClientId()) + .setModificationTime(tm().getTransactionTime()) + .setParent(domainKey) + .setPeriod(Period.create(1, YEARS)) + .setReason( + "Domain autorenewal by ExpandRecurringBillingEventsAction") + .setRequestedByRegistrar(false) + .setType(DOMAIN_AUTORENEW) + // Don't write a domain transaction record if the recurrence was + // ended prior to the billing time (i.e. a domain was deleted + // during the autorenew grace period). + .setDomainTransactionRecords( + recurring.getRecurrenceEndTime().isBefore(billingTime) + ? ImmutableSet.of() + : ImmutableSet.of( + DomainTransactionRecord.create( + tld.getTldStr(), + // We report this when the autorenew grace period + // ends + billingTime, + TransactionReportField.netRenewsFieldFromYears(1), + 1))) + .build(); + historyEntriesBuilder.add(historyEntry); - DateTime eventTime = billingTime.minus(tld.getAutoRenewGracePeriodLength()); - // Determine the cost for a one-year renewal. - Money renewCost = getDomainRenewCost(recurring.getTargetId(), eventTime, 1); - syntheticOneTimesBuilder.add(new OneTime.Builder() - .setBillingTime(billingTime) - .setClientId(recurring.getClientId()) - .setCost(renewCost) - .setEventTime(eventTime) - .setFlags(union(recurring.getFlags(), Flag.SYNTHETIC)) - .setParent(historyEntry) - .setPeriodYears(1) - .setReason(recurring.getReason()) - .setSyntheticCreationTime(executeTime) - .setCancellationMatchingBillingEvent(Key.create(recurring)) - .setTargetId(recurring.getTargetId()) - .build()); - } - Set historyEntries = historyEntriesBuilder.build(); - Set syntheticOneTimes = syntheticOneTimesBuilder.build(); - if (!isDryRun) { - ImmutableSet entitiesToSave = - new ImmutableSet.Builder() - .addAll(historyEntries) - .addAll(syntheticOneTimes) - .build(); - ofy().save().entities(entitiesToSave).now(); - } - return syntheticOneTimes.size(); - }); + DateTime eventTime = billingTime.minus(tld.getAutoRenewGracePeriodLength()); + // Determine the cost for a one-year renewal. + Money renewCost = getDomainRenewCost(recurring.getTargetId(), eventTime, 1); + syntheticOneTimesBuilder.add( + new OneTime.Builder() + .setBillingTime(billingTime) + .setClientId(recurring.getClientId()) + .setCost(renewCost) + .setEventTime(eventTime) + .setFlags(union(recurring.getFlags(), Flag.SYNTHETIC)) + .setParent(historyEntry) + .setPeriodYears(1) + .setReason(recurring.getReason()) + .setSyntheticCreationTime(executeTime) + .setCancellationMatchingBillingEvent(recurring.createVKey()) + .setTargetId(recurring.getTargetId()) + .build()); + } + Set historyEntries = historyEntriesBuilder.build(); + Set syntheticOneTimes = syntheticOneTimesBuilder.build(); + if (!isDryRun) { + ImmutableSet entitiesToSave = + new ImmutableSet.Builder() + .addAll(historyEntries) + .addAll(syntheticOneTimes) + .build(); + ofy().save().entities(entitiesToSave).now(); + } + return syntheticOneTimes.size(); + }); } catch (Throwable t) { getContext().incrementCounter("error: " + t.getClass().getSimpleName()); getContext().incrementCounter(ERROR_COUNTER); @@ -279,7 +290,8 @@ public class ExpandRecurringBillingEventsAction implements Runnable { return Streams.stream(oneTimesForDomain) .filter( billingEvent -> - Key.create(recurringEvent) + recurringEvent + .createVKey() .equals(billingEvent.getCancellationMatchingBillingEvent())) .map(OneTime::getBillingTime) .collect(toImmutableSet()); diff --git a/core/src/main/java/google/registry/flows/domain/DomainCreateFlow.java b/core/src/main/java/google/registry/flows/domain/DomainCreateFlow.java index 57e95f2b9..17fda5853 100644 --- a/core/src/main/java/google/registry/flows/domain/DomainCreateFlow.java +++ b/core/src/main/java/google/registry/flows/domain/DomainCreateFlow.java @@ -534,7 +534,7 @@ public class DomainCreateFlow implements TransactionalFlow { .setPeriodYears(years) .setCost(feesAndCredits.getCreateCost()) .setEventTime(now) - .setAllocationToken(allocationToken.map(Key::create).orElse(null)) + .setAllocationToken(allocationToken.map(AllocationToken::createVKey).orElse(null)) .setBillingTime( now.plus( isAnchorTenant diff --git a/core/src/main/java/google/registry/model/ModelUtils.java b/core/src/main/java/google/registry/model/ModelUtils.java index 7146fbd6d..a69ceb577 100644 --- a/core/src/main/java/google/registry/model/ModelUtils.java +++ b/core/src/main/java/google/registry/model/ModelUtils.java @@ -34,6 +34,7 @@ import com.googlecode.objectify.Key; import com.googlecode.objectify.annotation.Id; import com.googlecode.objectify.annotation.Ignore; import com.googlecode.objectify.annotation.Parent; +import google.registry.persistence.VKey; import java.lang.reflect.Array; import java.lang.reflect.Field; import java.lang.reflect.Modifier; @@ -139,10 +140,13 @@ public class ModelUtils { // If the field's type is the same as the field's class object, then it's a non-parameterized // type, and thus we just add it directly. We also don't bother looking at the parameterized - // types of Key objects, since they are just references to other objects and don't actually - // embed themselves in the persisted object anyway. + // types of Key and VKey objects, since they are just references to other objects and don't + // actually embed themselves in the persisted object anyway. Class fieldClazz = field.getType(); Type fieldType = field.getGenericType(); + if (VKey.class.equals(fieldClazz)) { + continue; + } builder.add(fieldClazz); if (fieldType.equals(fieldClazz) || Key.class.equals(clazz)) { continue; diff --git a/core/src/main/java/google/registry/model/billing/BillingEvent.java b/core/src/main/java/google/registry/model/billing/BillingEvent.java index 696cb3285..7184e60c0 100644 --- a/core/src/main/java/google/registry/model/billing/BillingEvent.java +++ b/core/src/main/java/google/registry/model/billing/BillingEvent.java @@ -30,6 +30,7 @@ import com.google.common.collect.Sets; import com.googlecode.objectify.Key; import com.googlecode.objectify.annotation.Entity; import com.googlecode.objectify.annotation.Id; +import com.googlecode.objectify.annotation.Ignore; import com.googlecode.objectify.annotation.IgnoreSave; import com.googlecode.objectify.annotation.Index; import com.googlecode.objectify.annotation.Parent; @@ -43,14 +44,28 @@ import google.registry.model.domain.rgp.GracePeriodStatus; import google.registry.model.domain.token.AllocationToken; import google.registry.model.reporting.HistoryEntry; import google.registry.model.transfer.TransferData.TransferServerApproveEntity; +import google.registry.persistence.VKey; +import google.registry.persistence.WithLongVKey; import java.util.Objects; import java.util.Optional; import java.util.Set; import javax.annotation.Nullable; +import javax.persistence.AttributeOverride; +import javax.persistence.AttributeOverrides; +import javax.persistence.Column; +import javax.persistence.Embedded; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.MappedSuperclass; +import javax.persistence.Transient; import org.joda.money.Money; import org.joda.time.DateTime; /** A billable event in a domain's lifecycle. */ +@MappedSuperclass +@WithLongVKey public abstract class BillingEvent extends ImmutableObject implements Buildable, TransferServerApproveEntity { @@ -93,24 +108,41 @@ public abstract class BillingEvent extends ImmutableObject /** Entity id. */ @Id - long id; + @javax.persistence.Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + Long id; - @Parent - @DoNotHydrate - Key parent; + @Parent @DoNotHydrate @Transient Key parent; /** The registrar to bill. */ @Index + @Column(nullable = false) String clientId; + /** Revision id of the entry in DomainHistory table that ths bill belongs to. */ + // TODO(shicong): Add foreign key constraint when DomainHistory table is generated + @Ignore + @Column(nullable = false) + Long domainHistoryRevisionId; + + /** ID of the EPP resource that the bill is for. */ + // TODO(shicong): Add foreign key constraint when we expand DatastoreHelp for Postgresql + @Ignore + @Column(nullable = false) + String domainRepoId; + /** When this event was created. For recurring events, this is also the recurrence start time. */ @Index + @Column(nullable = false) DateTime eventTime; /** The reason for the bill. */ + @Enumerated(EnumType.STRING) + @Column(nullable = false) Reason reason; /** The fully qualified domain name of the domain that the bill is for. */ + @Column(name = "domain_name", nullable = false) String targetId; @Nullable @@ -120,6 +152,14 @@ public abstract class BillingEvent extends ImmutableObject return clientId; } + public long getDomainHistoryRevisionId() { + return domainHistoryRevisionId; + } + + public String getDomainRepoId() { + return domainRepoId; + } + public DateTime getEventTime() { return eventTime; } @@ -163,7 +203,7 @@ public abstract class BillingEvent extends ImmutableObject return thisCastToDerived(); } - public B setId(Long id) { + public B setId(long id) { getInstance().id = id; return thisCastToDerived(); } @@ -173,6 +213,16 @@ public abstract class BillingEvent extends ImmutableObject return thisCastToDerived(); } + public B setDomainHistoryRevisionId(long domainHistoryRevisionId) { + getInstance().domainHistoryRevisionId = domainHistoryRevisionId; + return thisCastToDerived(); + } + + public B setDomainRepoId(String domainRepoId) { + getInstance().domainRepoId = domainRepoId; + return thisCastToDerived(); + } + public B setEventTime(DateTime eventTime) { getInstance().eventTime = eventTime; return thisCastToDerived(); @@ -194,6 +244,7 @@ public abstract class BillingEvent extends ImmutableObject } public B setParent(Key parentKey) { + // TODO(shicong): Figure out how to set domainHistoryRevisionId and domainRepoId getInstance().parent = parentKey; return thisCastToDerived(); } @@ -213,9 +264,23 @@ public abstract class BillingEvent extends ImmutableObject /** A one-time billable event. */ @ReportedOn @Entity + @javax.persistence.Entity(name = "BillingEvent") + @javax.persistence.Table( + indexes = { + @javax.persistence.Index(columnList = "clientId"), + @javax.persistence.Index(columnList = "eventTime"), + @javax.persistence.Index(columnList = "billingTime"), + @javax.persistence.Index(columnList = "syntheticCreationTime"), + @javax.persistence.Index(columnList = "allocation_token_id") + }) + @AttributeOverride(name = "id", column = @Column(name = "billing_event_id")) public static class OneTime extends BillingEvent { /** The billable value. */ + @AttributeOverrides({ + @AttributeOverride(name = "money.amount", column = @Column(name = "cost_amount")), + @AttributeOverride(name = "money.currency", column = @Column(name = "cost_currency")) + }) Money cost; /** When the cost should be billed. */ @@ -223,8 +288,8 @@ public abstract class BillingEvent extends ImmutableObject DateTime billingTime; /** - * The period in years of the action being billed for, if applicable, otherwise null. - * Used for financial reporting. + * The period in years of the action being billed for, if applicable, otherwise null. Used for + * financial reporting. */ @IgnoreSave(IfNull.class) Integer periodYears = null; @@ -240,15 +305,21 @@ public abstract class BillingEvent extends ImmutableObject /** * For {@link Flag#SYNTHETIC} events, a {@link Key} to the {@link BillingEvent} from which this - * OneTime was created. This is needed in order to properly match billing events against - * {@link Cancellation}s. + * OneTime was created. This is needed in order to properly match billing events against {@link + * Cancellation}s. */ - Key cancellationMatchingBillingEvent; + @Column(name = "cancellation_matching_billing_recurrence_id") + VKey cancellationMatchingBillingEvent; /** * The {@link AllocationToken} used in the creation of this event, or null if one was not used. + * + *

TODO(shicong): Add foreign key constraint when AllocationToken schema is generated */ - @Index @Nullable Key allocationToken; + @Column(name = "allocation_token_id") + @Index + @Nullable + VKey allocationToken; public Money getCost() { return cost; @@ -266,14 +337,18 @@ public abstract class BillingEvent extends ImmutableObject return syntheticCreationTime; } - public Key getCancellationMatchingBillingEvent() { + public VKey getCancellationMatchingBillingEvent() { return cancellationMatchingBillingEvent; } - public Optional> getAllocationToken() { + public Optional> getAllocationToken() { return Optional.ofNullable(allocationToken); } + public VKey createVKey() { + return VKey.createOfy(getClass(), Key.create(this)); + } + @Override public Builder asBuilder() { return new Builder(clone(this)); @@ -311,12 +386,12 @@ public abstract class BillingEvent extends ImmutableObject } public Builder setCancellationMatchingBillingEvent( - Key cancellationMatchingBillingEvent) { + VKey cancellationMatchingBillingEvent) { getInstance().cancellationMatchingBillingEvent = cancellationMatchingBillingEvent; return this; } - public Builder setAllocationToken(@Nullable Key allocationToken) { + public Builder setAllocationToken(@Nullable VKey allocationToken) { getInstance().allocationToken = allocationToken; return this; } @@ -361,6 +436,15 @@ public abstract class BillingEvent extends ImmutableObject */ @ReportedOn @Entity + @javax.persistence.Entity(name = "BillingRecurrence") + @javax.persistence.Table( + indexes = { + @javax.persistence.Index(columnList = "clientId"), + @javax.persistence.Index(columnList = "eventTime"), + @javax.persistence.Index(columnList = "recurrenceEndTime"), + @javax.persistence.Index(columnList = "recurrence_time_of_year") + }) + @AttributeOverride(name = "id", column = @Column(name = "billing_recurrence_id")) public static class Recurring extends BillingEvent { /** @@ -384,6 +468,10 @@ public abstract class BillingEvent extends ImmutableObject * model, whereas the billing time is a fixed {@link org.joda.time.Duration} later. */ @Index + @Embedded + @AttributeOverrides({ + @AttributeOverride(name = "timeString", column = @Column(name = "recurrence_time_of_year")) + }) TimeOfYear recurrenceTimeOfYear; public DateTime getRecurrenceEndTime() { @@ -394,6 +482,10 @@ public abstract class BillingEvent extends ImmutableObject return recurrenceTimeOfYear; } + public VKey createVKey() { + return VKey.createOfy(getClass(), Key.create(this)); + } + @Override public Builder asBuilder() { return new Builder(clone(this)); @@ -434,6 +526,14 @@ public abstract class BillingEvent extends ImmutableObject */ @ReportedOn @Entity + @javax.persistence.Entity(name = "BillingCancellation") + @javax.persistence.Table( + indexes = { + @javax.persistence.Index(columnList = "clientId"), + @javax.persistence.Index(columnList = "eventTime"), + @javax.persistence.Index(columnList = "billingTime") + }) + @AttributeOverride(name = "id", column = @Column(name = "billing_cancellation_id")) public static class Cancellation extends BillingEvent { /** The billing time of the charge that is being cancelled. */ @@ -446,7 +546,8 @@ public abstract class BillingEvent extends ImmutableObject *

Although the type is {@link Key} the name "ref" is preserved for historical reasons. */ @IgnoreSave(IfNull.class) - Key refOneTime = null; + @Column(name = "billing_event_id") + VKey refOneTime = null; /** * The recurring billing event to cancel, or null for non-autorenew cancellations. @@ -454,13 +555,14 @@ public abstract class BillingEvent extends ImmutableObject *

Although the type is {@link Key} the name "ref" is preserved for historical reasons. */ @IgnoreSave(IfNull.class) - Key refRecurring = null; + @Column(name = "billing_recurrence_id") + VKey refRecurring = null; public DateTime getBillingTime() { return billingTime; } - public Key getEventKey() { + public VKey getEventKey() { return firstNonNull(refOneTime, refRecurring); } @@ -492,13 +594,19 @@ public abstract class BillingEvent extends ImmutableObject .setParent(historyEntry); // Set the grace period's billing event using the appropriate Cancellation builder method. if (gracePeriod.getOneTimeBillingEvent() != null) { - builder.setOneTimeEventKey(gracePeriod.getOneTimeBillingEvent()); + builder.setOneTimeEventKey( + VKey.createOfy(BillingEvent.OneTime.class, gracePeriod.getOneTimeBillingEvent())); } else if (gracePeriod.getRecurringBillingEvent() != null) { - builder.setRecurringEventKey(gracePeriod.getRecurringBillingEvent()); + builder.setRecurringEventKey( + VKey.createOfy(BillingEvent.Recurring.class, gracePeriod.getRecurringBillingEvent())); } return builder.build(); } + public VKey createVKey() { + return VKey.createOfy(getClass(), Key.create(this)); + } + @Override public Builder asBuilder() { return new Builder(clone(this)); @@ -518,12 +626,12 @@ public abstract class BillingEvent extends ImmutableObject return this; } - public Builder setOneTimeEventKey(Key eventKey) { + public Builder setOneTimeEventKey(VKey eventKey) { getInstance().refOneTime = eventKey; return this; } - public Builder setRecurringEventKey(Key eventKey) { + public Builder setRecurringEventKey(VKey eventKey) { getInstance().refRecurring = eventKey; return this; } @@ -540,9 +648,7 @@ public abstract class BillingEvent extends ImmutableObject } } - /** - * An event representing a modification of an existing one-time billing event. - */ + /** An event representing a modification of an existing one-time billing event. */ @ReportedOn @Entity public static class Modification extends BillingEvent { diff --git a/core/src/main/java/google/registry/model/common/TimeOfYear.java b/core/src/main/java/google/registry/model/common/TimeOfYear.java index 984a6b62a..e08ad0d6c 100644 --- a/core/src/main/java/google/registry/model/common/TimeOfYear.java +++ b/core/src/main/java/google/registry/model/common/TimeOfYear.java @@ -29,20 +29,22 @@ import com.googlecode.objectify.annotation.Embed; import com.googlecode.objectify.annotation.Index; import google.registry.model.ImmutableObject; import java.util.List; +import javax.persistence.Embeddable; import org.joda.time.DateTime; /** * A time of year (month, day, millis of day) that can be stored in a sort-friendly format. * - *

This is conceptually similar to {@code MonthDay} in Joda or more generally to Joda's - * {@code Partial}, but the parts we need are too simple to justify a full implementation of - * {@code Partial}. + *

This is conceptually similar to {@code MonthDay} in Joda or more generally to Joda's {@code + * Partial}, but the parts we need are too simple to justify a full implementation of {@code + * Partial}. * *

For simplicity, the native representation of this class's data is its stored format. This * allows it to be embeddable with no translation needed and also delays parsing of the string on * load until it's actually needed. */ @Embed +@Embeddable public class TimeOfYear extends ImmutableObject { /** diff --git a/core/src/main/java/google/registry/model/domain/token/AllocationToken.java b/core/src/main/java/google/registry/model/domain/token/AllocationToken.java index b4d4be37e..f40ac4d90 100644 --- a/core/src/main/java/google/registry/model/domain/token/AllocationToken.java +++ b/core/src/main/java/google/registry/model/domain/token/AllocationToken.java @@ -45,6 +45,8 @@ import google.registry.model.common.TimedTransitionProperty; import google.registry.model.common.TimedTransitionProperty.TimeMapper; import google.registry.model.common.TimedTransitionProperty.TimedTransition; import google.registry.model.reporting.HistoryEntry; +import google.registry.persistence.VKey; +import google.registry.persistence.WithStringVKey; import java.util.Optional; import java.util.Set; import javax.annotation.Nullable; @@ -53,6 +55,7 @@ import org.joda.time.DateTime; /** An entity representing an allocation token. */ @ReportedOn @Entity +@WithStringVKey public class AllocationToken extends BackupGroupRoot implements Buildable { // Promotions should only move forward, and ENDED / CANCELLED are terminal states. @@ -179,6 +182,10 @@ public class AllocationToken extends BackupGroupRoot implements Buildable { return tokenStatusTransitions; } + public VKey createVKey() { + return VKey.createOfy(getClass(), Key.create(this)); + } + @Override public Builder asBuilder() { return new Builder(clone(this)); diff --git a/core/src/main/java/google/registry/model/ofy/ObjectifyService.java b/core/src/main/java/google/registry/model/ofy/ObjectifyService.java index d45d3903d..f8e1b1db6 100644 --- a/core/src/main/java/google/registry/model/ofy/ObjectifyService.java +++ b/core/src/main/java/google/registry/model/ofy/ObjectifyService.java @@ -34,6 +34,7 @@ import com.googlecode.objectify.annotation.EntitySubclass; import com.googlecode.objectify.impl.translate.TranslatorFactory; import com.googlecode.objectify.impl.translate.opt.joda.MoneyStringTranslatorFactory; import google.registry.config.RegistryEnvironment; +import google.registry.model.Buildable; import google.registry.model.EntityClasses; import google.registry.model.ImmutableObject; import google.registry.model.translators.BloomFilterOfStringTranslatorFactory; @@ -167,10 +168,16 @@ public class ObjectifyService { } com.googlecode.objectify.ObjectifyService.register(clazz); // Autogenerated ids make the commit log code very difficult since we won't always be able - // to create a key for an entity immediately when requesting a save. Disallow that here. - checkState( - !factory().getMetadata(clazz).getKeyMetadata().isIdGeneratable(), - "Can't register %s: Autogenerated ids (@Id on a Long) are not supported.", kind); + // to create a key for an entity immediately when requesting a save. So, we require such + // entities to implement google.registry.model.Buildable as its build() function allocates the + // id to the entity. + if (factory().getMetadata(clazz).getKeyMetadata().isIdGeneratable()) { + checkState( + Buildable.class.isAssignableFrom(clazz), + "Can't register %s: Entity with autogenerated ids (@Id on a Long) must implement" + + " google.registry.model.Buildable.", + kind); + } } } diff --git a/core/src/main/java/google/registry/persistence/converter/BillingEventFlagSetConverter.java b/core/src/main/java/google/registry/persistence/converter/BillingEventFlagSetConverter.java new file mode 100644 index 000000000..6a0cb1fd6 --- /dev/null +++ b/core/src/main/java/google/registry/persistence/converter/BillingEventFlagSetConverter.java @@ -0,0 +1,35 @@ +// 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.billing.BillingEvent.Flag; +import java.util.Set; +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; + +/** JPA {@link AttributeConverter} for storing/retrieving {@link Set}. */ +@Converter(autoApply = true) +public class BillingEventFlagSetConverter extends StringSetConverterBase { + + @Override + String toString(Flag element) { + return element.name(); + } + + @Override + Flag fromString(String value) { + return Flag.valueOf(value); + } +} diff --git a/core/src/main/resources/META-INF/persistence.xml b/core/src/main/resources/META-INF/persistence.xml index 2ed878d4d..0535b1416 100644 --- a/core/src/main/resources/META-INF/persistence.xml +++ b/core/src/main/resources/META-INF/persistence.xml @@ -19,6 +19,9 @@ * 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.billing.BillingEvent$Cancellation + google.registry.model.billing.BillingEvent$OneTime + google.registry.model.billing.BillingEvent$Recurring google.registry.model.contact.ContactResource google.registry.model.domain.DomainBase google.registry.model.host.HostResource @@ -36,6 +39,7 @@ google.registry.persistence.converter.BillingCostTransitionConverter + google.registry.persistence.converter.BillingEventFlagSetConverter google.registry.persistence.converter.BloomFilterConverter google.registry.persistence.converter.CidrAddressBlockListConverter google.registry.persistence.converter.CreateAutoTimestampConverter @@ -53,6 +57,8 @@ google.registry.persistence.converter.ZonedDateTimeConverter + google.registry.model.billing.VKeyConverter_BillingEvent + google.registry.model.domain.token.VKeyConverter_AllocationToken google.registry.model.host.VKeyConverter_HostResource google.registry.model.contact.VKeyConverter_ContactResource diff --git a/core/src/test/java/google/registry/batch/ExpandRecurringBillingEventsActionTest.java b/core/src/test/java/google/registry/batch/ExpandRecurringBillingEventsActionTest.java index 6ce1e8665..0cc5e2e6d 100644 --- a/core/src/test/java/google/registry/batch/ExpandRecurringBillingEventsActionTest.java +++ b/core/src/test/java/google/registry/batch/ExpandRecurringBillingEventsActionTest.java @@ -155,7 +155,7 @@ public class ExpandRecurringBillingEventsActionTest .setPeriodYears(1) .setReason(Reason.RENEW) .setSyntheticCreationTime(beginningOfTest) - .setCancellationMatchingBillingEvent(Key.create(recurring)) + .setCancellationMatchingBillingEvent(recurring.createVKey()) .setTargetId(domain.getFullyQualifiedDomainName()); } @@ -274,10 +274,12 @@ public class ExpandRecurringBillingEventsActionTest .setParent(persistedEntries.get(0)) .build(); // Persist an otherwise identical billing event that differs only in recurring event key. - BillingEvent.OneTime persisted = expected.asBuilder() - .setParent(persistedEntries.get(1)) - .setCancellationMatchingBillingEvent(Key.create(recurring2)) - .build(); + BillingEvent.OneTime persisted = + expected + .asBuilder() + .setParent(persistedEntries.get(1)) + .setCancellationMatchingBillingEvent(recurring2.createVKey()) + .build(); assertCursorAt(beginningOfTest); assertBillingEventsForResource(domain, persisted, expected, recurring, recurring2); } @@ -604,19 +606,21 @@ public class ExpandRecurringBillingEventsActionTest assertHistoryEntryMatches( domain, persistedEntries.get(0), "TheRegistrar", DateTime.parse("2000-02-19T00:00:00Z"), true); - BillingEvent.OneTime expected = defaultOneTimeBuilder() - .setParent(persistedEntries.get(0)) - .setCancellationMatchingBillingEvent(Key.create(recurring)) - .build(); + BillingEvent.OneTime expected = + defaultOneTimeBuilder() + .setParent(persistedEntries.get(0)) + .setCancellationMatchingBillingEvent(recurring.createVKey()) + .build(); assertHistoryEntryMatches( domain, persistedEntries.get(1), "TheRegistrar", DateTime.parse("2000-05-20T00:00:00Z"), true); - BillingEvent.OneTime expected2 = defaultOneTimeBuilder() - .setBillingTime(DateTime.parse("2000-05-20T00:00:00Z")) - .setEventTime(DateTime.parse("2000-04-05T00:00:00Z")) - .setParent(persistedEntries.get(1)) - .setCancellationMatchingBillingEvent(Key.create(recurring2)) - .build(); + BillingEvent.OneTime expected2 = + defaultOneTimeBuilder() + .setBillingTime(DateTime.parse("2000-05-20T00:00:00Z")) + .setEventTime(DateTime.parse("2000-04-05T00:00:00Z")) + .setParent(persistedEntries.get(1)) + .setCancellationMatchingBillingEvent(recurring2.createVKey()) + .build(); assertBillingEventsForResource(domain, expected, expected2, recurring, recurring2); assertCursorAt(beginningOfTest); } diff --git a/core/src/test/java/google/registry/flows/EppTestCase.java b/core/src/test/java/google/registry/flows/EppTestCase.java index 9f1f59136..d0ea303f4 100644 --- a/core/src/test/java/google/registry/flows/EppTestCase.java +++ b/core/src/test/java/google/registry/flows/EppTestCase.java @@ -41,6 +41,7 @@ import google.registry.model.registry.Registry; import google.registry.model.reporting.HistoryEntry; import google.registry.model.reporting.HistoryEntry.Type; import google.registry.monitoring.whitebox.EppMetric; +import google.registry.persistence.VKey; import google.registry.testing.FakeClock; import google.registry.testing.FakeHttpSession; import google.registry.testing.FakeResponse; @@ -339,7 +340,8 @@ public class EppTestCase extends ShardableTestCase { .setTargetId(domain.getFullyQualifiedDomainName()) .setClientId(domain.getCurrentSponsorClientId()) .setEventTime(deleteTime) - .setOneTimeEventKey(findKeyToActualOneTimeBillingEvent(billingEventToCancel)) + .setOneTimeEventKey( + VKey.createOfy(OneTime.class, findKeyToActualOneTimeBillingEvent(billingEventToCancel))) .setBillingTime(createTime.plus(Registry.get(domain.getTld()).getAddGracePeriodLength())) .setReason(Reason.CREATE) .setParent(getOnlyHistoryEntryOfType(domain, Type.DOMAIN_DELETE)) @@ -353,7 +355,8 @@ public class EppTestCase extends ShardableTestCase { .setTargetId(domain.getFullyQualifiedDomainName()) .setClientId(domain.getCurrentSponsorClientId()) .setEventTime(deleteTime) - .setOneTimeEventKey(findKeyToActualOneTimeBillingEvent(billingEventToCancel)) + .setOneTimeEventKey( + VKey.createOfy(OneTime.class, findKeyToActualOneTimeBillingEvent(billingEventToCancel))) .setBillingTime(renewTime.plus(Registry.get(domain.getTld()).getRenewGracePeriodLength())) .setReason(Reason.RENEW) .setParent(getOnlyHistoryEntryOfType(domain, Type.DOMAIN_DELETE)) diff --git a/core/src/test/java/google/registry/flows/domain/DomainCreateFlowTest.java b/core/src/test/java/google/registry/flows/domain/DomainCreateFlowTest.java index 198929750..6deb30a37 100644 --- a/core/src/test/java/google/registry/flows/domain/DomainCreateFlowTest.java +++ b/core/src/test/java/google/registry/flows/domain/DomainCreateFlowTest.java @@ -276,7 +276,7 @@ public class DomainCreateFlowTest extends ResourceFlowTestCase jpaTm().saveNew(billingEvent)); + } + + @Test + public void testCloudSqlPersistence_OneTime() { + saveRegistrar("a registrar"); + saveNewBillingEvent(sqlOneTime); + + BillingEvent.OneTime persisted = + jpaTm() + .transact( + () -> jpaTm().load(VKey.createSql(BillingEvent.OneTime.class, sqlOneTime.id))); + // TODO(shicong): Remove these fixes after the entities are fully compatible + BillingEvent.OneTime fixed = + persisted + .asBuilder() + .setParent(sqlOneTime.getParentKey()) + .setAllocationToken(sqlOneTime.getAllocationToken().get()) + .build(); + assertThat(fixed).isEqualTo(sqlOneTime); + } + + @Test + public void testCloudSqlPersistence_Cancellation() { + saveRegistrar("a registrar"); + saveNewBillingEvent(sqlOneTime); + VKey sqlVKey = VKey.createSql(BillingEvent.OneTime.class, sqlOneTime.id); + BillingEvent sqlCancellationOneTime = + cancellationOneTime + .asBuilder() + .setOneTimeEventKey(sqlVKey) + .setDomainRepoId(domain.getRepoId()) + .setDomainHistoryRevisionId(1L) + .build(); + saveNewBillingEvent(sqlCancellationOneTime); + + BillingEvent.Cancellation persisted = + jpaTm() + .transact( + () -> + jpaTm() + .load( + VKey.createSql( + BillingEvent.Cancellation.class, sqlCancellationOneTime.id))); + // TODO(shicong): Remove these fixes after the entities are fully compatible + BillingEvent.Cancellation fixed = + persisted + .asBuilder() + .setParent(sqlCancellationOneTime.getParentKey()) + .setOneTimeEventKey(sqlVKey) + .build(); + assertThat(fixed).isEqualTo(sqlCancellationOneTime); + } + + @Test + public void testCloudSqlPersistence_Recurring() { + saveRegistrar("a registrar"); + BillingEvent.Recurring sqlRecurring = + recurring + .asBuilder() + .setDomainRepoId(domain.getRepoId()) + .setDomainHistoryRevisionId(1L) + .build(); + saveNewBillingEvent(sqlRecurring); + + BillingEvent.Recurring persisted = + jpaTm() + .transact( + () -> jpaTm().load(VKey.createSql(BillingEvent.Recurring.class, sqlRecurring.id))); + // TODO(shicong): Remove these fixes after the entities are fully compatible + BillingEvent.Recurring fixed = + persisted.asBuilder().setParent(sqlRecurring.getParentKey()).build(); + assertThat(fixed).isEqualTo(sqlRecurring); + } + @Test public void testPersistence() { assertThat(ofy().load().entity(oneTime).now()).isEqualTo(oneTime); @@ -183,8 +289,13 @@ public class BillingEventTest extends EntityTestCase { @Test public void testCancellationMatching() { - Key recurringKey = ofy().load().entity(oneTimeSynthetic).now() - .getCancellationMatchingBillingEvent(); + Key recurringKey = + ofy() + .load() + .entity(oneTimeSynthetic) + .now() + .getCancellationMatchingBillingEvent() + .getOfyKey(); assertThat(ofy().load().key(recurringKey).now()).isEqualTo(recurring); } @@ -219,7 +330,7 @@ public class BillingEventTest extends EntityTestCase { oneTime .asBuilder() .setFlags(ImmutableSet.of(BillingEvent.Flag.SYNTHETIC)) - .setCancellationMatchingBillingEvent(Key.create(recurring)) + .setCancellationMatchingBillingEvent(recurring.createVKey()) .build()); assertThat(thrown) .hasMessageThat() @@ -263,7 +374,7 @@ public class BillingEventTest extends EntityTestCase { () -> oneTime .asBuilder() - .setCancellationMatchingBillingEvent(Key.create(recurring)) + .setCancellationMatchingBillingEvent(recurring.createVKey()) .build()); assertThat(thrown) .hasMessageThat() @@ -334,8 +445,8 @@ public class BillingEventTest extends EntityTestCase { () -> cancellationOneTime .asBuilder() - .setOneTimeEventKey(Key.create(oneTime)) - .setRecurringEventKey(Key.create(recurring)) + .setOneTimeEventKey(oneTime.createVKey()) + .setRecurringEventKey(recurring.createVKey()) .build()); assertThat(thrown).hasMessageThat().contains("exactly one billing event"); } diff --git a/core/src/test/java/google/registry/persistence/transaction/JpaEntityCoverage.java b/core/src/test/java/google/registry/persistence/transaction/JpaEntityCoverage.java index b0ad2044b..7ec954c86 100644 --- a/core/src/test/java/google/registry/persistence/transaction/JpaEntityCoverage.java +++ b/core/src/test/java/google/registry/persistence/transaction/JpaEntityCoverage.java @@ -45,7 +45,7 @@ public class JpaEntityCoverage extends ExternalResource { private static final ImmutableSet ALL_JPA_ENTITIES = PersistenceXmlUtility.getManagedClasses().stream() .filter(e -> !IGNORE_ENTITIES.contains(e.getSimpleName())) - .filter(e -> e.getAnnotation(Entity.class) != null) + .filter(e -> e.isAnnotationPresent(Entity.class)) .collect(ImmutableSet.toImmutableSet()); private static final Set allCoveredJpaEntities = Sets.newHashSet(); // Map of test class name to boolean flag indicating if it tests any JPA entities. 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 935b3b194..f2f66ed76 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.billing.BillingEventTest; import google.registry.model.contact.ContactResourceTest; import google.registry.model.domain.DomainBaseSqlTest; import google.registry.model.registry.RegistryLockDaoTest; @@ -68,6 +69,7 @@ import org.junit.runner.RunWith; @SelectClasses({ // BeforeSuiteTest must be the first entry. See class javadoc for details. BeforeSuiteTest.class, + BillingEventTest.class, ClaimsListDaoTest.class, ContactResourceTest.class, CursorDaoTest.class, diff --git a/core/src/test/resources/google/registry/model/schema.txt b/core/src/test/resources/google/registry/model/schema.txt index 75870b662..52562bc8d 100644 --- a/core/src/test/resources/google/registry/model/schema.txt +++ b/core/src/test/resources/google/registry/model/schema.txt @@ -5,11 +5,11 @@ class google.registry.model.UpdateAutoTimestamp { org.joda.time.DateTime timestamp; } class google.registry.model.billing.BillingEvent$Cancellation { - @Id long id; + @Id java.lang.Long id; @Parent com.googlecode.objectify.Key parent; - com.googlecode.objectify.Key refOneTime; - com.googlecode.objectify.Key refRecurring; google.registry.model.billing.BillingEvent$Reason reason; + google.registry.persistence.VKey refOneTime; + google.registry.persistence.VKey refRecurring; java.lang.String clientId; java.lang.String targetId; java.util.Set flags; @@ -26,7 +26,7 @@ enum google.registry.model.billing.BillingEvent$Flag { SYNTHETIC; } class google.registry.model.billing.BillingEvent$Modification { - @Id long id; + @Id java.lang.Long id; @Parent com.googlecode.objectify.Key parent; com.googlecode.objectify.Key eventRef; google.registry.model.billing.BillingEvent$Reason reason; @@ -38,11 +38,11 @@ class google.registry.model.billing.BillingEvent$Modification { org.joda.time.DateTime eventTime; } class google.registry.model.billing.BillingEvent$OneTime { - @Id long id; + @Id java.lang.Long id; @Parent com.googlecode.objectify.Key parent; - com.googlecode.objectify.Key cancellationMatchingBillingEvent; - com.googlecode.objectify.Key allocationToken; google.registry.model.billing.BillingEvent$Reason reason; + google.registry.persistence.VKey cancellationMatchingBillingEvent; + google.registry.persistence.VKey allocationToken; java.lang.Integer periodYears; java.lang.String clientId; java.lang.String targetId; @@ -62,7 +62,7 @@ enum google.registry.model.billing.BillingEvent$Reason { TRANSFER; } class google.registry.model.billing.BillingEvent$Recurring { - @Id long id; + @Id java.lang.Long id; @Parent com.googlecode.objectify.Key parent; google.registry.model.billing.BillingEvent$Reason reason; google.registry.model.common.TimeOfYear recurrenceTimeOfYear; diff --git a/db/src/main/resources/sql/flyway/V26__create_billing_event.sql b/db/src/main/resources/sql/flyway/V26__create_billing_event.sql new file mode 100644 index 000000000..3f1798d98 --- /dev/null +++ b/db/src/main/resources/sql/flyway/V26__create_billing_event.sql @@ -0,0 +1,104 @@ +-- 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 "BillingCancellation" ( + billing_cancellation_id bigserial not null, + client_id text not null, + domain_history_revision_id int8 not null, + domain_repo_id text not null, + event_time timestamptz not null, + flags text[], + reason text not null, + domain_name text not null, + billing_time timestamptz, + billing_event_id int8, + billing_recurrence_id int8, + primary key (billing_cancellation_id) +); + +create table "BillingEvent" ( + billing_event_id bigserial not null, + client_id text not null, + domain_history_revision_id int8 not null, + domain_repo_id text not null, + event_time timestamptz not null, + flags text[], + reason text not null, + domain_name text not null, + allocation_token_id text, + billing_time timestamptz, + cancellation_matching_billing_recurrence_id int8, + cost_amount numeric(19, 2), + cost_currency text, + period_years int4, + synthetic_creation_time timestamptz, + primary key (billing_event_id) +); + +create table "BillingRecurrence" ( + billing_recurrence_id bigserial not null, + client_id text not null, + domain_history_revision_id int8 not null, + domain_repo_id text not null, + event_time timestamptz not null, + flags text[], + reason text not null, + domain_name text not null, + recurrence_end_time timestamptz, + recurrence_time_of_year text, + primary key (billing_recurrence_id) +); + +create index IDXeokttmxtpq2hohcioe5t2242b on "BillingCancellation" (client_id); +create index IDX2exdfbx6oiiwnhr8j6gjpqt2j on "BillingCancellation" (event_time); +create index IDXqa3g92jc17e8dtiaviy4fet4x on "BillingCancellation" (billing_time); +create index IDX73l103vc5900ig3p4odf0cngt on "BillingEvent" (client_id); +create index IDX5yfbr88439pxw0v3j86c74fp8 on "BillingEvent" (event_time); +create index IDX6py6ocrab0ivr76srcd2okpnq on "BillingEvent" (billing_time); +create index IDXplxf9v56p0wg8ws6qsvd082hk on "BillingEvent" (synthetic_creation_time); +create index IDXhmv411mdqo5ibn4vy7ykxpmlv on "BillingEvent" (allocation_token_id); +create index IDXn898pb9mwcg359cdwvolb11ck on "BillingRecurrence" (client_id); +create index IDX6syykou4nkc7hqa5p8r92cpch on "BillingRecurrence" (event_time); +create index IDXp3usbtvk0v1m14i5tdp4xnxgc on "BillingRecurrence" (recurrence_end_time); +create index IDXjny8wuot75b5e6p38r47wdawu on "BillingRecurrence" (recurrence_time_of_year); + +alter table if exists "BillingEvent" + add constraint fk_billing_event_client_id + foreign key (client_id) + references "Registrar"; + +alter table if exists "BillingEvent" + add constraint fk_billing_event_cancellation_matching_billing_recurrence_id + foreign key (cancellation_matching_billing_recurrence_id) + references "BillingRecurrence"; + +alter table if exists "BillingCancellation" + add constraint fk_billing_cancellation_client_id + foreign key (client_id) + references "Registrar"; + +alter table if exists "BillingCancellation" + add constraint fk_billing_cancellation_billing_event_id + foreign key (billing_event_id) + references "BillingEvent"; + +alter table if exists "BillingCancellation" + add constraint fk_billing_cancellation_billing_recurrence_id + foreign key (billing_recurrence_id) + references "BillingRecurrence"; + +alter table if exists "BillingRecurrence" + add constraint fk_billing_recurrence_client_id + foreign key (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 838a02aea..dccafedd2 100644 --- a/db/src/main/resources/sql/schema/db-schema.sql.generated +++ b/db/src/main/resources/sql/schema/db-schema.sql.generated @@ -12,6 +12,54 @@ -- See the License for the specific language governing permissions and -- limitations under the License. + create table "BillingCancellation" ( + billing_cancellation_id bigserial not null, + client_id text not null, + domain_history_revision_id int8 not null, + domain_repo_id text not null, + event_time timestamptz not null, + flags text[], + reason text not null, + domain_name text not null, + billing_time timestamptz, + billing_event_id int8, + billing_recurrence_id int8, + primary key (billing_cancellation_id) + ); + + create table "BillingEvent" ( + billing_event_id bigserial not null, + client_id text not null, + domain_history_revision_id int8 not null, + domain_repo_id text not null, + event_time timestamptz not null, + flags text[], + reason text not null, + domain_name text not null, + allocation_token_id text, + billing_time timestamptz, + cancellation_matching_billing_recurrence_id int8, + cost_amount numeric(19, 2), + cost_currency text, + period_years int4, + synthetic_creation_time timestamptz, + primary key (billing_event_id) + ); + + create table "BillingRecurrence" ( + billing_recurrence_id bigserial not null, + client_id text not null, + domain_history_revision_id int8 not null, + domain_repo_id text not null, + event_time timestamptz not null, + flags text[], + reason text not null, + domain_name text not null, + recurrence_end_time timestamptz, + recurrence_time_of_year text, + primary key (billing_recurrence_id) + ); + create table "ClaimsEntry" ( revision_id int8 not null, claim_key text not null, @@ -280,6 +328,18 @@ should_publish boolean not null, primary key (revision_id) ); +create index IDXeokttmxtpq2hohcioe5t2242b on "BillingCancellation" (client_id); +create index IDX2exdfbx6oiiwnhr8j6gjpqt2j on "BillingCancellation" (event_time); +create index IDXqa3g92jc17e8dtiaviy4fet4x on "BillingCancellation" (billing_time); +create index IDX73l103vc5900ig3p4odf0cngt on "BillingEvent" (client_id); +create index IDX5yfbr88439pxw0v3j86c74fp8 on "BillingEvent" (event_time); +create index IDX6py6ocrab0ivr76srcd2okpnq on "BillingEvent" (billing_time); +create index IDXplxf9v56p0wg8ws6qsvd082hk on "BillingEvent" (synthetic_creation_time); +create index IDXhmv411mdqo5ibn4vy7ykxpmlv on "BillingEvent" (allocation_token_id); +create index IDXn898pb9mwcg359cdwvolb11ck on "BillingRecurrence" (client_id); +create index IDX6syykou4nkc7hqa5p8r92cpch on "BillingRecurrence" (event_time); +create index IDXp3usbtvk0v1m14i5tdp4xnxgc on "BillingRecurrence" (recurrence_end_time); +create index IDXjny8wuot75b5e6p38r47wdawu on "BillingRecurrence" (recurrence_time_of_year); create index IDX3y752kr9uh4kh6uig54vemx0l on "Contact" (creation_time); create index IDXbn8t4wp85fgxjl8q4ctlscx55 on "Contact" (current_sponsor_client_id); create index IDXn1f711wicdnooa2mqb7g1m55o on "Contact" (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 3370beb18..4ebe5e776 100644 --- a/db/src/main/resources/sql/schema/nomulus.golden.sql +++ b/db/src/main/resources/sql/schema/nomulus.golden.sql @@ -34,6 +34,123 @@ SET default_tablespace = ''; SET default_with_oids = false; +-- +-- Name: BillingCancellation; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public."BillingCancellation" ( + billing_cancellation_id bigint NOT NULL, + client_id text NOT NULL, + domain_history_revision_id bigint NOT NULL, + domain_repo_id text NOT NULL, + event_time timestamp with time zone NOT NULL, + flags text[], + reason text NOT NULL, + domain_name text NOT NULL, + billing_time timestamp with time zone, + billing_event_id bigint, + billing_recurrence_id bigint +); + + +-- +-- Name: BillingCancellation_billing_cancellation_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public."BillingCancellation_billing_cancellation_id_seq" + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: BillingCancellation_billing_cancellation_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public."BillingCancellation_billing_cancellation_id_seq" OWNED BY public."BillingCancellation".billing_cancellation_id; + + +-- +-- Name: BillingEvent; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public."BillingEvent" ( + billing_event_id bigint NOT NULL, + client_id text NOT NULL, + domain_history_revision_id bigint NOT NULL, + domain_repo_id text NOT NULL, + event_time timestamp with time zone NOT NULL, + flags text[], + reason text NOT NULL, + domain_name text NOT NULL, + allocation_token_id text, + billing_time timestamp with time zone, + cancellation_matching_billing_recurrence_id bigint, + cost_amount numeric(19,2), + cost_currency text, + period_years integer, + synthetic_creation_time timestamp with time zone +); + + +-- +-- Name: BillingEvent_billing_event_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public."BillingEvent_billing_event_id_seq" + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: BillingEvent_billing_event_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public."BillingEvent_billing_event_id_seq" OWNED BY public."BillingEvent".billing_event_id; + + +-- +-- Name: BillingRecurrence; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public."BillingRecurrence" ( + billing_recurrence_id bigint NOT NULL, + client_id text NOT NULL, + domain_history_revision_id bigint NOT NULL, + domain_repo_id text NOT NULL, + event_time timestamp with time zone NOT NULL, + flags text[], + reason text NOT NULL, + domain_name text NOT NULL, + recurrence_end_time timestamp with time zone, + recurrence_time_of_year text +); + + +-- +-- Name: BillingRecurrence_billing_recurrence_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public."BillingRecurrence_billing_recurrence_id_seq" + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: BillingRecurrence_billing_recurrence_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public."BillingRecurrence_billing_recurrence_id_seq" OWNED BY public."BillingRecurrence".billing_recurrence_id; + + -- -- Name: ClaimsEntry; Type: TABLE; Schema: public; Owner: - -- @@ -427,6 +544,27 @@ CREATE SEQUENCE public."ReservedList_revision_id_seq" ALTER SEQUENCE public."ReservedList_revision_id_seq" OWNED BY public."ReservedList".revision_id; +-- +-- Name: BillingCancellation billing_cancellation_id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public."BillingCancellation" ALTER COLUMN billing_cancellation_id SET DEFAULT nextval('public."BillingCancellation_billing_cancellation_id_seq"'::regclass); + + +-- +-- Name: BillingEvent billing_event_id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public."BillingEvent" ALTER COLUMN billing_event_id SET DEFAULT nextval('public."BillingEvent_billing_event_id_seq"'::regclass); + + +-- +-- Name: BillingRecurrence billing_recurrence_id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public."BillingRecurrence" ALTER COLUMN billing_recurrence_id SET DEFAULT nextval('public."BillingRecurrence_billing_recurrence_id_seq"'::regclass); + + -- -- Name: ClaimsList revision_id; Type: DEFAULT; Schema: public; Owner: - -- @@ -455,6 +593,30 @@ ALTER TABLE ONLY public."RegistryLock" ALTER COLUMN revision_id SET DEFAULT next ALTER TABLE ONLY public."ReservedList" ALTER COLUMN revision_id SET DEFAULT nextval('public."ReservedList_revision_id_seq"'::regclass); +-- +-- Name: BillingCancellation BillingCancellation_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public."BillingCancellation" + ADD CONSTRAINT "BillingCancellation_pkey" PRIMARY KEY (billing_cancellation_id); + + +-- +-- Name: BillingEvent BillingEvent_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public."BillingEvent" + ADD CONSTRAINT "BillingEvent_pkey" PRIMARY KEY (billing_event_id); + + +-- +-- Name: BillingRecurrence BillingRecurrence_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public."BillingRecurrence" + ADD CONSTRAINT "BillingRecurrence_pkey" PRIMARY KEY (billing_recurrence_id); + + -- -- Name: ClaimsEntry ClaimsEntry_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- @@ -597,6 +759,13 @@ CREATE INDEX idx1p3esngcwwu6hstyua6itn6ff ON public."Contact" USING btree (searc CREATE INDEX idx1rcgkdd777bpvj0r94sltwd5y ON public."Domain" USING btree (fully_qualified_domain_name); +-- +-- Name: idx2exdfbx6oiiwnhr8j6gjpqt2j; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idx2exdfbx6oiiwnhr8j6gjpqt2j ON public."BillingCancellation" USING btree (event_time); + + -- -- Name: idx3y752kr9uh4kh6uig54vemx0l; Type: INDEX; Schema: public; Owner: - -- @@ -611,6 +780,34 @@ CREATE INDEX idx3y752kr9uh4kh6uig54vemx0l ON public."Contact" USING btree (creat CREATE INDEX idx5mnf0wn20tno4b9do88j61klr ON public."Domain" USING btree (deletion_time); +-- +-- Name: idx5yfbr88439pxw0v3j86c74fp8; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idx5yfbr88439pxw0v3j86c74fp8 ON public."BillingEvent" USING btree (event_time); + + +-- +-- Name: idx6py6ocrab0ivr76srcd2okpnq; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idx6py6ocrab0ivr76srcd2okpnq ON public."BillingEvent" USING btree (billing_time); + + +-- +-- Name: idx6syykou4nkc7hqa5p8r92cpch; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idx6syykou4nkc7hqa5p8r92cpch ON public."BillingRecurrence" USING btree (event_time); + + +-- +-- Name: idx73l103vc5900ig3p4odf0cngt; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idx73l103vc5900ig3p4odf0cngt ON public."BillingEvent" USING btree (client_id); + + -- -- Name: idx8nr0ke9mrrx4ewj6pd2ag4rmr; Type: INDEX; Schema: public; Owner: - -- @@ -639,6 +836,27 @@ CREATE INDEX idx_registry_lock_verification_code ON public."RegistryLock" USING CREATE INDEX idxbn8t4wp85fgxjl8q4ctlscx55 ON public."Contact" USING btree (current_sponsor_client_id); +-- +-- Name: idxeokttmxtpq2hohcioe5t2242b; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idxeokttmxtpq2hohcioe5t2242b ON public."BillingCancellation" USING btree (client_id); + + +-- +-- Name: idxhmv411mdqo5ibn4vy7ykxpmlv; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idxhmv411mdqo5ibn4vy7ykxpmlv ON public."BillingEvent" USING btree (allocation_token_id); + + +-- +-- Name: idxjny8wuot75b5e6p38r47wdawu; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idxjny8wuot75b5e6p38r47wdawu ON public."BillingRecurrence" USING btree (recurrence_time_of_year); + + -- -- Name: idxkjt9yaq92876dstimd93hwckh; Type: INDEX; Schema: public; Owner: - -- @@ -653,6 +871,34 @@ CREATE INDEX idxkjt9yaq92876dstimd93hwckh ON public."Domain" USING btree (curren CREATE INDEX idxn1f711wicdnooa2mqb7g1m55o ON public."Contact" USING btree (deletion_time); +-- +-- Name: idxn898pb9mwcg359cdwvolb11ck; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idxn898pb9mwcg359cdwvolb11ck ON public."BillingRecurrence" USING btree (client_id); + + +-- +-- Name: idxp3usbtvk0v1m14i5tdp4xnxgc; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idxp3usbtvk0v1m14i5tdp4xnxgc ON public."BillingRecurrence" USING btree (recurrence_end_time); + + +-- +-- Name: idxplxf9v56p0wg8ws6qsvd082hk; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idxplxf9v56p0wg8ws6qsvd082hk ON public."BillingEvent" USING btree (synthetic_creation_time); + + +-- +-- Name: idxqa3g92jc17e8dtiaviy4fet4x; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idxqa3g92jc17e8dtiaviy4fet4x ON public."BillingCancellation" USING btree (billing_time); + + -- -- Name: idxrwl38wwkli1j7gkvtywi9jokq; Type: INDEX; Schema: public; Owner: - -- @@ -751,6 +997,54 @@ ALTER TABLE ONLY public."Contact" ADD CONSTRAINT fk93c185fx7chn68uv7nl6uv2s0 FOREIGN KEY (current_sponsor_client_id) REFERENCES public."Registrar"(client_id); +-- +-- Name: BillingCancellation fk_billing_cancellation_billing_event_id; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public."BillingCancellation" + ADD CONSTRAINT fk_billing_cancellation_billing_event_id FOREIGN KEY (billing_event_id) REFERENCES public."BillingEvent"(billing_event_id); + + +-- +-- Name: BillingCancellation fk_billing_cancellation_billing_recurrence_id; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public."BillingCancellation" + ADD CONSTRAINT fk_billing_cancellation_billing_recurrence_id FOREIGN KEY (billing_recurrence_id) REFERENCES public."BillingRecurrence"(billing_recurrence_id); + + +-- +-- Name: BillingCancellation fk_billing_cancellation_client_id; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public."BillingCancellation" + ADD CONSTRAINT fk_billing_cancellation_client_id FOREIGN KEY (client_id) REFERENCES public."Registrar"(client_id); + + +-- +-- Name: BillingEvent fk_billing_event_cancellation_matching_billing_recurrence_id; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public."BillingEvent" + ADD CONSTRAINT fk_billing_event_cancellation_matching_billing_recurrence_id FOREIGN KEY (cancellation_matching_billing_recurrence_id) REFERENCES public."BillingRecurrence"(billing_recurrence_id); + + +-- +-- Name: BillingEvent fk_billing_event_client_id; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public."BillingEvent" + ADD CONSTRAINT fk_billing_event_client_id FOREIGN KEY (client_id) REFERENCES public."Registrar"(client_id); + + +-- +-- Name: BillingRecurrence fk_billing_recurrence_client_id; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public."BillingRecurrence" + ADD CONSTRAINT fk_billing_recurrence_client_id FOREIGN KEY (client_id) REFERENCES public."Registrar"(client_id); + + -- -- Name: Domain fk_domain_admin_contact; Type: FK CONSTRAINT; Schema: public; Owner: - --