From aa2898ebfc0187be35865cd6107e34acda489889 Mon Sep 17 00:00:00 2001 From: Lai Jiang Date: Thu, 3 Jun 2021 10:21:16 -0400 Subject: [PATCH] Make ExpandRecurringBillingEventAction SQL-aware (#1181) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There is some complication regarding how the CancellationMatchingBillingEvent of the generated OneTime can be reconstructed when loading from SQL. I decided to only address it in testing as there is no real value to fully reconstruct this VKey in production where we are either in SQL or Ofy mode, both never in both. Therefore the VKey in a particular mode only needs to contain the corresponding key in order to function. --- This change is [Reviewable](https://reviewable.io/reviews/google/nomulus/1181) --- .../ExpandRecurringBillingEventsAction.java | 367 +- .../registry/model/billing/BillingEvent.java | 50 +- .../model/reporting/HistoryEntryDao.java | 1 + ...xpandRecurringBillingEventsActionTest.java | 479 +- .../sql/er_diagram/brief_er_diagram.html | 2829 ++++----- .../sql/er_diagram/full_er_diagram.html | 5433 +++++++++-------- db/src/main/resources/sql/flyway.txt | 1 + ...ecurrence_history_id_column_to_onetime.sql | 21 + .../sql/schema/db-schema.sql.generated | 1 + .../resources/sql/schema/nomulus.golden.sql | 11 +- 10 files changed, 4755 insertions(+), 4438 deletions(-) create mode 100644 db/src/main/resources/sql/flyway/V97__add_recurrence_history_id_column_to_onetime.sql diff --git a/core/src/main/java/google/registry/batch/ExpandRecurringBillingEventsAction.java b/core/src/main/java/google/registry/batch/ExpandRecurringBillingEventsAction.java index f1d2d632b..70d57b37e 100644 --- a/core/src/main/java/google/registry/batch/ExpandRecurringBillingEventsAction.java +++ b/core/src/main/java/google/registry/batch/ExpandRecurringBillingEventsAction.java @@ -21,9 +21,12 @@ import static google.registry.mapreduce.MapreduceRunner.PARAM_DRY_RUN; import static google.registry.mapreduce.inputs.EppResourceInputs.createChildEntityInput; import static google.registry.model.common.Cursor.CursorType.RECURRING_BILLING; import static google.registry.model.domain.Period.Unit.YEARS; -import static google.registry.model.ofy.ObjectifyService.ofy; +import static google.registry.model.ofy.ObjectifyService.auditedOfy; import static google.registry.model.reporting.HistoryEntry.Type.DOMAIN_AUTORENEW; +import static google.registry.persistence.transaction.QueryComposer.Comparator.EQ; +import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm; import static google.registry.persistence.transaction.TransactionManagerFactory.tm; +import static google.registry.persistence.transaction.TransactionManagerUtil.transactIfJpaTm; import static google.registry.pricing.PricingEngineProxy.getDomainRenewCost; import static google.registry.util.CollectionUtils.union; import static google.registry.util.DateTimeUtils.START_OF_TIME; @@ -38,10 +41,8 @@ import com.google.common.collect.ImmutableSet; import com.google.common.collect.Range; import com.google.common.collect.Streams; import com.google.common.flogger.FluentLogger; -import com.googlecode.objectify.Key; import google.registry.mapreduce.MapreduceRunner; import google.registry.mapreduce.inputs.NullInput; -import google.registry.model.EppResource; import google.registry.model.ImmutableObject; import google.registry.model.billing.BillingEvent; import google.registry.model.billing.BillingEvent.Flag; @@ -54,7 +55,7 @@ import google.registry.model.domain.Period; import google.registry.model.registry.Registry; import google.registry.model.reporting.DomainTransactionRecord; import google.registry.model.reporting.DomainTransactionRecord.TransactionReportField; -import google.registry.model.reporting.HistoryEntry; +import google.registry.persistence.VKey; import google.registry.request.Action; import google.registry.request.Parameter; import google.registry.request.Response; @@ -92,31 +93,87 @@ public class ExpandRecurringBillingEventsAction implements Runnable { @Override public void run() { - Cursor cursor = - tm().loadByKeyIfPresent(Cursor.createGlobalVKey(RECURRING_BILLING)).orElse(null); DateTime executeTime = clock.nowUtc(); - DateTime persistedCursorTime = (cursor == null ? START_OF_TIME : cursor.getCursorTime()); + DateTime persistedCursorTime = + transactIfJpaTm( + () -> + tm().loadByKeyIfPresent(Cursor.createGlobalVKey(RECURRING_BILLING)) + .orElse(Cursor.createGlobal(RECURRING_BILLING, START_OF_TIME)) + .getCursorTime()); DateTime cursorTime = cursorTimeParam.orElse(persistedCursorTime); checkArgument( - cursorTime.isBefore(executeTime), - "Cursor time must be earlier than execution time."); + cursorTime.isBefore(executeTime), "Cursor time must be earlier than execution time."); logger.atInfo().log( "Running Recurring billing event expansion for billing time range [%s, %s).", cursorTime, executeTime); - mrRunner - .setJobName("Expand Recurring billing events into synthetic OneTime events.") - .setModuleName("backend") - .runMapreduce( - new ExpandRecurringBillingEventsMapper(isDryRun, cursorTime, clock.nowUtc()), - new ExpandRecurringBillingEventsReducer(isDryRun, persistedCursorTime), - // Add an extra shard that maps over a null recurring event (see the mapper for why). - ImmutableList.of( - new NullInput<>(), - createChildEntityInput( - ImmutableSet.of(DomainBase.class), ImmutableSet.of(Recurring.class)))) - .sendLinkToMapreduceConsole(response); - } + if (tm().isOfy()) { + mrRunner + .setJobName("Expand Recurring billing events into synthetic OneTime events.") + .setModuleName("backend") + .runMapreduce( + new ExpandRecurringBillingEventsMapper(isDryRun, cursorTime, clock.nowUtc()), + new ExpandRecurringBillingEventsReducer(isDryRun, persistedCursorTime), + // Add an extra shard that maps over a null recurring event (see the mapper for why). + ImmutableList.of( + new NullInput<>(), + createChildEntityInput( + ImmutableSet.of(DomainBase.class), ImmutableSet.of(Recurring.class)))) + .sendLinkToMapreduceConsole(response); + } else { + int numBillingEventsSaved = + jpaTm() + .transact( + () -> + jpaTm() + .query( + "FROM BillingRecurrence " + + "WHERE event_time <= :executeTime " + + "AND event_time < recurrence_end_time", + Recurring.class) + .setParameter("executeTime", executeTime.toDate()) + // Need to get a list from the transaction and then convert it to a stream + // for further processing. If we get a stream directly, each elements gets + // processed downstream eagerly but Hibernate returns a + // ScrollableResultsIterator that cannot be advanced outside the + // transaction, resulting in an exception. + .getResultList()) + .stream() + .map( + recurring -> + jpaTm() + .transact( + () -> + expandBillingEvent(recurring, executeTime, cursorTime, isDryRun))) + .reduce(0, Integer::sum); + if (!isDryRun) { + logger.atInfo().log("Saved OneTime billing events", numBillingEventsSaved); + } else { + logger.atInfo().log("Generated OneTime billing events (dry run)", numBillingEventsSaved); + } + logger.atInfo().log( + "Recurring event expansion %s complete for billing event range [%s, %s).", + isDryRun ? "(dry run) " : "", cursorTime, executeTime); + tm().transact( + () -> { + // Check for the unlikely scenario where the cursor has been altered during the + // expansion. + DateTime currentCursorTime = + tm().loadByKeyIfPresent(Cursor.createGlobalVKey(RECURRING_BILLING)) + .orElse(Cursor.createGlobal(RECURRING_BILLING, START_OF_TIME)) + .getCursorTime(); + if (!currentCursorTime.equals(persistedCursorTime)) { + throw new IllegalStateException( + String.format( + "Current cursor position %s does not match persisted cursor position %s.", + currentCursorTime, persistedCursorTime)); + } + if (!isDryRun) { + tm().put(Cursor.createGlobal(RECURRING_BILLING, executeTime)); + } + }); + } + } /** Mapper to expand {@link Recurring} billing events into synthetic {@link OneTime} events. */ public static class ExpandRecurringBillingEventsMapper extends Mapper { @@ -155,100 +212,7 @@ public class ExpandRecurringBillingEventsAction implements Runnable { try { 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))); - - // 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); - - // 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 - DomainHistory historyEntry = - new DomainHistory.Builder() - .setBySuperuser(false) - .setClientId(recurring.getClientId()) - .setModificationTime(tm().getTransactionTime()) - // TODO (jianglai): modify this to use setDomain instead when - // converting this action to be SQL-aware. - .setDomainRepoId(domainKey.getName()) - .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(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(); - }); + () -> expandBillingEvent(recurring, executeTime, cursorTime, isDryRun)); } catch (Throwable t) { getContext().incrementCounter("error: " + t.getClass().getSimpleName()); getContext().incrementCounter(ERROR_COUNTER); @@ -260,45 +224,12 @@ public class ExpandRecurringBillingEventsAction implements Runnable { if (!isDryRun) { getContext().incrementCounter("Saved OneTime billing events", numBillingEventsSaved); } else { - getContext().incrementCounter( - "Generated OneTime billing events (dry run)", numBillingEventsSaved); + getContext() + .incrementCounter("Generated OneTime billing events (dry run)", numBillingEventsSaved); } } - - /** - * Filters a set of {@link DateTime}s down to event times that are in scope for a particular - * mapreduce run, given the cursor time and the mapreduce execution time. - */ - private ImmutableSet getBillingTimesInScope( - Iterable eventTimes, - DateTime cursorTime, - DateTime executeTime, - final Registry tld) { - return Streams.stream(eventTimes) - .map(eventTime -> eventTime.plus(tld.getAutoRenewGracePeriodLength())) - .filter(Range.closedOpen(cursorTime, executeTime)) - .collect(toImmutableSet()); - } - - /** - * Determines an {@link ImmutableSet} of {@link DateTime}s that have already been persisted - * for a given recurring billing event. - */ - private ImmutableSet getExistingBillingTimes( - Iterable oneTimesForDomain, - final BillingEvent.Recurring recurringEvent) { - return Streams.stream(oneTimesForDomain) - .filter( - billingEvent -> - recurringEvent - .createVKey() - .equals(billingEvent.getCancellationMatchingBillingEvent())) - .map(OneTime::getBillingTime) - .collect(toImmutableSet()); - } } - /** * "Reducer" to advance the cursor after all map jobs have been completed. The NullInput into the * mapper will cause the mapper to emit one timestamp pair (current cursor and execution time), @@ -331,7 +262,8 @@ public class ExpandRecurringBillingEventsAction implements Runnable { isDryRun ? "(dry run) " : "", cursorTime, executionTime); tm().transact( () -> { - Cursor cursor = ofy().load().key(Cursor.createGlobalKey(RECURRING_BILLING)).now(); + Cursor cursor = + auditedOfy().load().key(Cursor.createGlobalKey(RECURRING_BILLING)).now(); DateTime currentCursorTime = (cursor == null ? START_OF_TIME : cursor.getCursorTime()); if (!currentCursorTime.equals(expectedPersistedCursorTime)) { @@ -346,4 +278,135 @@ public class ExpandRecurringBillingEventsAction implements Runnable { }); } } + + private static int expandBillingEvent( + Recurring recurring, DateTime executeTime, DateTime cursorTime, boolean isDryRun) { + 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))); + + // Convert these event times to billing times + final ImmutableSet billingTimes = + getBillingTimesInScope(eventTimes, cursorTime, executeTime, tld); + + VKey domainKey = + VKey.create( + DomainBase.class, recurring.getDomainRepoId(), recurring.getParentKey().getParent()); + Iterable oneTimesForDomain; + if (tm().isOfy()) { + oneTimesForDomain = auditedOfy().load().type(OneTime.class).ancestor(domainKey.getOfyKey()); + } else { + oneTimesForDomain = + tm().createQueryComposer(OneTime.class) + .where("domainRepoId", EQ, recurring.getDomainRepoId()) + .list(); + } + + // 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 + DomainHistory historyEntry = + new DomainHistory.Builder() + .setBySuperuser(false) + .setClientId(recurring.getClientId()) + .setModificationTime(tm().getTransactionTime()) + .setDomain(tm().loadByKey(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(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(); + tm().putAll(entitiesToSave); + } + return syntheticOneTimes.size(); + } + + /** + * Filters a set of {@link DateTime}s down to event times that are in scope for a particular + * mapreduce run, given the cursor time and the mapreduce execution time. + */ + protected static ImmutableSet getBillingTimesInScope( + Iterable eventTimes, + DateTime cursorTime, + DateTime executeTime, + final Registry tld) { + return Streams.stream(eventTimes) + .map(eventTime -> eventTime.plus(tld.getAutoRenewGracePeriodLength())) + .filter(Range.closedOpen(cursorTime, executeTime)) + .collect(toImmutableSet()); + } + + /** + * Determines an {@link ImmutableSet} of {@link DateTime}s that have already been persisted for a + * given recurring billing event. + */ + private static ImmutableSet getExistingBillingTimes( + Iterable oneTimesForDomain, + final BillingEvent.Recurring recurringEvent) { + return Streams.stream(oneTimesForDomain) + .filter( + billingEvent -> + recurringEvent + .createVKey() + .equals(billingEvent.getCancellationMatchingBillingEvent())) + .map(OneTime::getBillingTime) + .collect(toImmutableSet()); + } } 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 d8f209112..714ec63dc 100644 --- a/core/src/main/java/google/registry/model/billing/BillingEvent.java +++ b/core/src/main/java/google/registry/model/billing/BillingEvent.java @@ -324,13 +324,22 @@ public abstract class BillingEvent extends ImmutableObject DateTime syntheticCreationTime; /** - * 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. + * For {@link Flag#SYNTHETIC} events, a {@link Key} to the {@link Recurring} from which this + * {@link OneTime} was created. This is needed in order to properly match billing events against + * {@link Cancellation}s. */ @Column(name = "cancellation_matching_billing_recurrence_id") VKey cancellationMatchingBillingEvent; + /** + * For {@link Flag#SYNTHETIC} events, the {@link DomainHistory} revision ID of the the {@link + * Recurring} from which this {@link OneTime} was created. This is needed in order to recreate + * the {@link VKey} when reading from SQL. + */ + @Ignore + @Column(name = "recurrence_history_revision_id") + Long recurringEventHistoryRevisionId; + /** * The {@link AllocationToken} used in the creation of this event, or null if one was not used. */ @@ -354,10 +363,14 @@ public abstract class BillingEvent extends ImmutableObject return syntheticCreationTime; } - public VKey getCancellationMatchingBillingEvent() { + public VKey getCancellationMatchingBillingEvent() { return cancellationMatchingBillingEvent; } + public Long getRecurringEventHistoryRevisionId() { + return recurringEventHistoryRevisionId; + } + public Optional> getAllocationToken() { return Optional.ofNullable(allocationToken); } @@ -376,6 +389,28 @@ public abstract class BillingEvent extends ImmutableObject return new Builder(clone(this)); } + @Override + void onLoad() { + super.onLoad(); + if (cancellationMatchingBillingEvent != null) { + recurringEventHistoryRevisionId = + cancellationMatchingBillingEvent.getOfyKey().getParent().getId(); + } + } + + @Override + void postLoad() { + super.postLoad(); + if (cancellationMatchingBillingEvent != null) { + cancellationMatchingBillingEvent = + cancellationMatchingBillingEvent.restoreOfy( + DomainBase.class, + domainRepoId, + DomainHistory.class, + recurringEventHistoryRevisionId); + } + } + /** A builder for {@link OneTime} since it is immutable. */ public static class Builder extends BillingEvent.Builder { @@ -410,6 +445,8 @@ public abstract class BillingEvent extends ImmutableObject public Builder setCancellationMatchingBillingEvent( VKey cancellationMatchingBillingEvent) { getInstance().cancellationMatchingBillingEvent = cancellationMatchingBillingEvent; + getInstance().recurringEventHistoryRevisionId = + cancellationMatchingBillingEvent.getOfyKey().getParent().getId(); return this; } @@ -444,6 +481,11 @@ public abstract class BillingEvent extends ImmutableObject == (instance.cancellationMatchingBillingEvent != null), "Cancellation matching billing event must be set if and only if the SYNTHETIC flag " + "is set."); + checkState( + !instance.getFlags().contains(Flag.SYNTHETIC) + || (instance.cancellationMatchingBillingEvent.getOfyKey().getParent().getId() + == instance.recurringEventHistoryRevisionId), + "Cancellation matching billing event and its history revision ID does not match."); return super.build(); } } diff --git a/core/src/main/java/google/registry/model/reporting/HistoryEntryDao.java b/core/src/main/java/google/registry/model/reporting/HistoryEntryDao.java index da578c598..a312e68c6 100644 --- a/core/src/main/java/google/registry/model/reporting/HistoryEntryDao.java +++ b/core/src/main/java/google/registry/model/reporting/HistoryEntryDao.java @@ -184,6 +184,7 @@ public class HistoryEntryDao { .where("modificationTime", criteriaBuilder::greaterThanOrEqualTo, afterTime) .where("modificationTime", criteriaBuilder::lessThanOrEqualTo, beforeTime) .where(repoIdFieldName, criteriaBuilder::equal, parentKey.getSqlKey().toString()) + .orderByAsc("id") .build(); return ImmutableList.sortedCopyOf( diff --git a/core/src/test/java/google/registry/batch/ExpandRecurringBillingEventsActionTest.java b/core/src/test/java/google/registry/batch/ExpandRecurringBillingEventsActionTest.java index 4c5389dd3..4d411d049 100644 --- a/core/src/test/java/google/registry/batch/ExpandRecurringBillingEventsActionTest.java +++ b/core/src/test/java/google/registry/batch/ExpandRecurringBillingEventsActionTest.java @@ -21,6 +21,7 @@ import static google.registry.model.ofy.ObjectifyService.auditedOfy; import static google.registry.model.reporting.HistoryEntry.Type.DOMAIN_AUTORENEW; import static google.registry.model.reporting.HistoryEntry.Type.DOMAIN_CREATE; import static google.registry.persistence.transaction.TransactionManagerFactory.tm; +import static google.registry.persistence.transaction.TransactionManagerUtil.transactIfJpaTm; import static google.registry.testing.DatabaseHelper.assertBillingEvents; import static google.registry.testing.DatabaseHelper.assertBillingEventsForResource; import static google.registry.testing.DatabaseHelper.createTld; @@ -52,9 +53,14 @@ import google.registry.model.registry.Registry; import google.registry.model.reporting.DomainTransactionRecord; import google.registry.model.reporting.DomainTransactionRecord.TransactionReportField; import google.registry.model.reporting.HistoryEntry; +import google.registry.testing.DualDatabaseTest; import google.registry.testing.FakeClock; import google.registry.testing.FakeResponse; import google.registry.testing.InjectExtension; +import google.registry.testing.ReplayExtension; +import google.registry.testing.TestOfyAndSql; +import google.registry.testing.TestOfyOnly; +import google.registry.testing.TestSqlOnly; import google.registry.testing.mapreduce.MapreduceTestCase; import java.util.ArrayList; import java.util.List; @@ -62,17 +68,25 @@ import java.util.Optional; import org.joda.money.Money; import org.joda.time.DateTime; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Order; import org.junit.jupiter.api.extension.RegisterExtension; /** Unit tests for {@link ExpandRecurringBillingEventsAction}. */ +@DualDatabaseTest public class ExpandRecurringBillingEventsActionTest extends MapreduceTestCase { - @RegisterExtension public final InjectExtension inject = new InjectExtension(); + private DateTime currentTestTime = DateTime.parse("1999-01-05T00:00:00Z"); + private final FakeClock clock = new FakeClock(currentTestTime); - private final DateTime beginningOfTest = DateTime.parse("2000-10-02T00:00:00Z"); - private final FakeClock clock = new FakeClock(beginningOfTest); + @Order(Order.DEFAULT - 1) + @RegisterExtension + public final InjectExtension inject = + new InjectExtension().withStaticFieldOverride(Ofy.class, "clock", clock); + + @Order(Order.DEFAULT - 2) + @RegisterExtension + public final ReplayExtension replayExtension = ReplayExtension.createWithCompare(clock); private DomainBase domain; private DomainHistory historyEntry; @@ -80,7 +94,6 @@ public class ExpandRecurringBillingEventsActionTest @BeforeEach void beforeEach() { - inject.setStaticField(Ofy.class, "clock", clock); action = new ExpandRecurringBillingEventsAction(); action.mrRunner = makeDefaultRunner(); action.clock = clock; @@ -111,27 +124,37 @@ public class ExpandRecurringBillingEventsActionTest .setRecurrenceEndTime(END_OF_TIME) .setTargetId(domain.getDomainName()) .build(); + currentTestTime = clock.nowUtc(); + clock.setTo(DateTime.parse("2000-10-02T00:00:00Z")); } private void saveCursor(final DateTime cursorTime) { tm().transact(() -> tm().put(Cursor.createGlobal(RECURRING_BILLING, cursorTime))); } - private void runMapreduce() throws Exception { + private void runAction() throws Exception { action.response = new FakeResponse(); action.run(); + // Need to save the current test time before running the mapreduce, which increments the clock. + // The execution time (e. g. transaction time) is captured when the action starts running so + // the passage of time afterward does not affect the timestamp stored in the billing events. + currentTestTime = clock.nowUtc(); executeTasksUntilEmpty("mapreduce", clock); auditedOfy().clearSessionCache(); } private void assertCursorAt(DateTime expectedCursorTime) { - Cursor cursor = auditedOfy().load().key(Cursor.createGlobalKey(RECURRING_BILLING)).now(); + Cursor cursor = + transactIfJpaTm(() -> tm().loadByKey(Cursor.createGlobalVKey(RECURRING_BILLING))); assertThat(cursor).isNotNull(); assertThat(cursor.getCursorTime()).isEqualTo(expectedCursorTime); } private void assertHistoryEntryMatches( - DomainBase domain, HistoryEntry actual, String clientId, DateTime billingTime, + DomainBase domain, + HistoryEntry actual, + String clientId, + DateTime billingTime, boolean shouldHaveTxRecord) { assertThat(actual.getBySuperuser()).isFalse(); assertThat(actual.getClientId()).isEqualTo(clientId); @@ -145,10 +168,7 @@ public class ExpandRecurringBillingEventsActionTest assertThat(actual.getDomainTransactionRecords()) .containsExactly( DomainTransactionRecord.create( - "tld", - billingTime, - TransactionReportField.NET_RENEWS_1_YR, - 1)); + "tld", billingTime, TransactionReportField.NET_RENEWS_1_YR, 1)); } else { assertThat(actual.getDomainTransactionRecords()).isEmpty(); } @@ -163,28 +183,26 @@ public class ExpandRecurringBillingEventsActionTest .setFlags(ImmutableSet.of(Flag.AUTO_RENEW, Flag.SYNTHETIC)) .setPeriodYears(1) .setReason(Reason.RENEW) - .setSyntheticCreationTime(beginningOfTest) + .setSyntheticCreationTime(currentTestTime) .setCancellationMatchingBillingEvent(recurring.createVKey()) .setTargetId(domain.getDomainName()); } - @Test + @TestOfyAndSql void testSuccess_expandSingleEvent() throws Exception { persistResource(recurring); action.cursorTimeParam = Optional.of(START_OF_TIME); - runMapreduce(); + runAction(); DomainHistory persistedEntry = getOnlyHistoryEntryOfType(domain, DOMAIN_AUTORENEW, DomainHistory.class); assertHistoryEntryMatches( domain, persistedEntry, "TheRegistrar", DateTime.parse("2000-02-19T00:00:00Z"), true); - BillingEvent.OneTime expected = defaultOneTimeBuilder() - .setParent(persistedEntry) - .build(); + BillingEvent.OneTime expected = defaultOneTimeBuilder().setParent(persistedEntry).build(); assertBillingEventsForResource(domain, expected, recurring); - assertCursorAt(beginningOfTest); + assertCursorAt(currentTestTime); } - @Test + @TestOfyAndSql void testSuccess_expandSingleEvent_deletedDomain() throws Exception { DateTime deletionTime = DateTime.parse("2000-08-01T00:00:00Z"); DomainBase deletedDomain = persistDeletedDomain("deleted.tld", deletionTime); @@ -209,7 +227,7 @@ public class ExpandRecurringBillingEventsActionTest .setTargetId(deletedDomain.getDomainName()) .build()); action.cursorTimeParam = Optional.of(START_OF_TIME); - runMapreduce(); + runAction(); DomainHistory persistedEntry = getOnlyHistoryEntryOfType(deletedDomain, DOMAIN_AUTORENEW, DomainHistory.class); assertHistoryEntryMatches( @@ -224,69 +242,69 @@ public class ExpandRecurringBillingEventsActionTest .setTargetId(deletedDomain.getDomainName()) .build(); assertBillingEventsForResource(deletedDomain, expected, recurring); - assertCursorAt(beginningOfTest); + assertCursorAt(currentTestTime); } - @Test + @TestOfyAndSql void testSuccess_expandSingleEvent_idempotentForDuplicateRuns() throws Exception { persistResource(recurring); action.cursorTimeParam = Optional.of(START_OF_TIME); - runMapreduce(); + runAction(); DomainHistory persistedEntry = getOnlyHistoryEntryOfType(domain, DOMAIN_AUTORENEW, DomainHistory.class); assertHistoryEntryMatches( domain, persistedEntry, "TheRegistrar", DateTime.parse("2000-02-19T00:00:00Z"), true); BillingEvent.OneTime expected = defaultOneTimeBuilder().setParent(persistedEntry).build(); - assertCursorAt(beginningOfTest); + assertCursorAt(currentTestTime); DateTime beginningOfSecondRun = clock.nowUtc(); action.response = new FakeResponse(); - runMapreduce(); + runAction(); assertCursorAt(beginningOfSecondRun); assertBillingEventsForResource(domain, expected, recurring); } - @Test + @TestOfyAndSql void testSuccess_expandSingleEvent_idempotentForExistingOneTime() throws Exception { persistResource(recurring); - BillingEvent.OneTime persisted = persistResource(defaultOneTimeBuilder() - .setParent(historyEntry) - .build()); + BillingEvent.OneTime persisted = + persistResource(defaultOneTimeBuilder().setParent(historyEntry).build()); action.cursorTimeParam = Optional.of(START_OF_TIME); - runMapreduce(); + runAction(); // No new history entries should be generated assertThat(getHistoryEntriesOfType(domain, DOMAIN_AUTORENEW)).isEmpty(); - assertCursorAt(beginningOfTest); + assertCursorAt(currentTestTime); // No additional billing events should be generated assertBillingEventsForResource(domain, persisted, recurring); } - @Test + @TestSqlOnly void testSuccess_expandSingleEvent_notIdempotentForDifferentBillingTime() throws Exception { persistResource(recurring); action.cursorTimeParam = Optional.of(START_OF_TIME); - runMapreduce(); + runAction(); DomainHistory persistedEntry = getOnlyHistoryEntryOfType(domain, DOMAIN_AUTORENEW, DomainHistory.class); assertHistoryEntryMatches( domain, persistedEntry, "TheRegistrar", DateTime.parse("2000-02-19T00:00:00Z"), true); BillingEvent.OneTime expected = defaultOneTimeBuilder().setParent(persistedEntry).build(); // Persist an otherwise identical billing event that differs only in billing time. - BillingEvent.OneTime persisted = persistResource(expected.asBuilder() - .setBillingTime(DateTime.parse("1999-02-19T00:00:00Z")) - .setEventTime(DateTime.parse("1999-01-05T00:00:00Z")) - .build()); - assertCursorAt(beginningOfTest); + BillingEvent.OneTime persisted = + persistResource( + expected + .asBuilder() + .setBillingTime(DateTime.parse("1999-02-19T00:00:00Z")) + .setEventTime(DateTime.parse("1999-01-05T00:00:00Z")) + .build()); + assertCursorAt(currentTestTime); assertBillingEventsForResource(domain, persisted, expected, recurring); } - @Test + @TestOfyAndSql void testSuccess_expandSingleEvent_notIdempotentForDifferentRecurring() throws Exception { persistResource(recurring); - BillingEvent.Recurring recurring2 = persistResource(recurring.asBuilder() - .setId(3L) - .build()); + BillingEvent.Recurring recurring2 = persistResource(recurring.asBuilder().setId(3L).build()); action.cursorTimeParam = Optional.of(START_OF_TIME); - runMapreduce(); + runAction(); List persistedEntries = getHistoryEntriesOfType(domain, DOMAIN_AUTORENEW, DomainHistory.class); for (HistoryEntry persistedEntry : persistedEntries) { @@ -294,9 +312,8 @@ public class ExpandRecurringBillingEventsActionTest domain, persistedEntry, "TheRegistrar", DateTime.parse("2000-02-19T00:00:00Z"), true); } assertThat(persistedEntries).hasSize(2); - BillingEvent.OneTime expected = defaultOneTimeBuilder() - .setParent(persistedEntries.get(0)) - .build(); + BillingEvent.OneTime expected = + defaultOneTimeBuilder().setParent(persistedEntries.get(0)).build(); // Persist an otherwise identical billing event that differs only in recurring event key. BillingEvent.OneTime persisted = expected @@ -304,164 +321,166 @@ public class ExpandRecurringBillingEventsActionTest .setParent(persistedEntries.get(1)) .setCancellationMatchingBillingEvent(recurring2.createVKey()) .build(); - assertCursorAt(beginningOfTest); + assertCursorAt(currentTestTime); assertBillingEventsForResource(domain, persisted, expected, recurring, recurring2); } - @Test + @TestOfyAndSql void testSuccess_ignoreRecurringBeforeWindow() throws Exception { - recurring = persistResource(recurring.asBuilder() - .setEventTime(DateTime.parse("1997-01-05T00:00:00Z")) - .setRecurrenceEndTime(DateTime.parse("1999-10-05T00:00:00Z")) - .build()); + recurring = + persistResource( + recurring + .asBuilder() + .setEventTime(DateTime.parse("1997-01-05T00:00:00Z")) + .setRecurrenceEndTime(DateTime.parse("1999-10-05T00:00:00Z")) + .build()); action.cursorTimeParam = Optional.of(DateTime.parse("2000-01-01T00:00:00Z")); - runMapreduce(); + runAction(); // No new history entries should be generated assertThat(getHistoryEntriesOfType(domain, DOMAIN_AUTORENEW)).isEmpty(); assertBillingEventsForResource(domain, recurring); - assertCursorAt(beginningOfTest); + assertCursorAt(currentTestTime); } - @Test + @TestOfyAndSql void testSuccess_ignoreRecurringAfterWindow() throws Exception { - recurring = persistResource(recurring.asBuilder() - .setEventTime(clock.nowUtc().plusYears(2)) - .build()); + recurring = + persistResource(recurring.asBuilder().setEventTime(clock.nowUtc().plusYears(2)).build()); action.cursorTimeParam = Optional.of(START_OF_TIME); - runMapreduce(); + runAction(); // No new history entries should be generated assertThat(getHistoryEntriesOfType(domain, DOMAIN_AUTORENEW)).isEmpty(); assertBillingEventsForResource(domain, recurring); } - @Test + @TestOfyAndSql void testSuccess_expandSingleEvent_billingTimeAtCursorTime() throws Exception { persistResource(recurring); action.cursorTimeParam = Optional.of(DateTime.parse("2000-02-19T00:00:00Z")); - runMapreduce(); + runAction(); DomainHistory persistedEntry = getOnlyHistoryEntryOfType(domain, DOMAIN_AUTORENEW, DomainHistory.class); assertHistoryEntryMatches( domain, persistedEntry, "TheRegistrar", DateTime.parse("2000-02-19T00:00:00Z"), true); BillingEvent.OneTime expected = defaultOneTimeBuilder().setParent(persistedEntry).build(); assertBillingEventsForResource(domain, expected, recurring); - assertCursorAt(beginningOfTest); + assertCursorAt(currentTestTime); } - @Test + @TestOfyAndSql void testSuccess_expandSingleEvent_cursorTimeBetweenEventAndBillingTime() throws Exception { persistResource(recurring); action.cursorTimeParam = Optional.of(DateTime.parse("2000-01-12T00:00:00Z")); - runMapreduce(); + runAction(); DomainHistory persistedEntry = getOnlyHistoryEntryOfType(domain, DOMAIN_AUTORENEW, DomainHistory.class); assertHistoryEntryMatches( domain, persistedEntry, "TheRegistrar", DateTime.parse("2000-02-19T00:00:00Z"), true); BillingEvent.OneTime expected = defaultOneTimeBuilder().setParent(persistedEntry).build(); assertBillingEventsForResource(domain, expected, recurring); - assertCursorAt(beginningOfTest); + assertCursorAt(currentTestTime); } - @Test + @TestOfyAndSql void testSuccess_expandSingleEvent_billingTimeAtExecutionTime() throws Exception { - DateTime testTime = DateTime.parse("2000-02-19T00:00:00Z").minusMillis(1); + clock.setTo(currentTestTime); persistResource(recurring); action.cursorTimeParam = Optional.of(START_OF_TIME); - // Clock is advanced one milli in runMapreduce() - clock.setTo(testTime); - runMapreduce(); + clock.setTo(DateTime.parse("2000-02-19T00:00:00Z")); + runAction(); // No new history entries should be generated assertThat(getHistoryEntriesOfType(domain, DOMAIN_AUTORENEW)).isEmpty(); // A candidate billing event is set to be billed exactly on 2/19/00 @ 00:00, // but these should not be generated as the interval is closed on cursorTime, open on // executeTime. assertBillingEventsForResource(domain, recurring); - assertCursorAt(testTime); + assertCursorAt(currentTestTime); } - @Test + @TestOfyAndSql void testSuccess_expandSingleEvent_multipleYearCreate() throws Exception { - DateTime testTime = beginningOfTest.plusYears(2); action.cursorTimeParam = Optional.of(recurring.getEventTime()); recurring = persistResource( recurring.asBuilder().setEventTime(recurring.getEventTime().plusYears(2)).build()); - clock.setTo(testTime); - runMapreduce(); + clock.setTo(DateTime.parse("2002-10-02T00:00:00Z")); + runAction(); DomainHistory persistedEntry = getOnlyHistoryEntryOfType(domain, DOMAIN_AUTORENEW, DomainHistory.class); assertHistoryEntryMatches( domain, persistedEntry, "TheRegistrar", DateTime.parse("2002-02-19T00:00:00Z"), true); - BillingEvent.OneTime expected = defaultOneTimeBuilder() - .setBillingTime(DateTime.parse("2002-02-19T00:00:00Z")) - .setEventTime(DateTime.parse("2002-01-05T00:00:00Z")) - .setParent(persistedEntry) - .setSyntheticCreationTime(testTime) - .build(); + BillingEvent.OneTime expected = + defaultOneTimeBuilder() + .setBillingTime(DateTime.parse("2002-02-19T00:00:00Z")) + .setEventTime(DateTime.parse("2002-01-05T00:00:00Z")) + .setParent(persistedEntry) + .build(); assertBillingEventsForResource(domain, expected, recurring); - assertCursorAt(testTime); + assertCursorAt(currentTestTime); } - @Test + @TestOfyAndSql void testSuccess_expandSingleEvent_withCursor() throws Exception { persistResource(recurring); saveCursor(START_OF_TIME); - runMapreduce(); + runAction(); DomainHistory persistedEntry = getOnlyHistoryEntryOfType(domain, DOMAIN_AUTORENEW, DomainHistory.class); assertHistoryEntryMatches( domain, persistedEntry, "TheRegistrar", DateTime.parse("2000-02-19T00:00:00Z"), true); BillingEvent.OneTime expected = defaultOneTimeBuilder().setParent(persistedEntry).build(); assertBillingEventsForResource(domain, expected, recurring); - assertCursorAt(beginningOfTest); + assertCursorAt(currentTestTime); } - @Test + @TestOfyAndSql void testSuccess_expandSingleEvent_withCursorPastExpected() throws Exception { persistResource(recurring); // Simulate a quick second run of the mapreduce (this should be a no-op). saveCursor(clock.nowUtc().minusSeconds(1)); - runMapreduce(); + runAction(); // No new history entries should be generated assertThat(getHistoryEntriesOfType(domain, DOMAIN_AUTORENEW)).isEmpty(); assertBillingEventsForResource(domain, recurring); - assertCursorAt(beginningOfTest); + assertCursorAt(currentTestTime); } - @Test + @TestOfyAndSql void testSuccess_expandSingleEvent_recurrenceEndBeforeEvent() throws Exception { // This can occur when a domain is transferred or deleted before a domain comes up for renewal. - recurring = persistResource(recurring.asBuilder() - .setRecurrenceEndTime(recurring.getEventTime().minusDays(5)) - .build()); + recurring = + persistResource( + recurring + .asBuilder() + .setRecurrenceEndTime(recurring.getEventTime().minusDays(5)) + .build()); action.cursorTimeParam = Optional.of(START_OF_TIME); - runMapreduce(); + runAction(); // No new history entries should be generated assertThat(getHistoryEntriesOfType(domain, DOMAIN_AUTORENEW)).isEmpty(); assertBillingEventsForResource(domain, recurring); - assertCursorAt(beginningOfTest); + assertCursorAt(currentTestTime); } - @Test + @TestOfyAndSql void testSuccess_expandSingleEvent_dryRun() throws Exception { persistResource(recurring); action.isDryRun = true; saveCursor(START_OF_TIME); // Need a saved cursor to verify that it didn't move. - runMapreduce(); + runAction(); // No new history entries should be generated assertThat(getHistoryEntriesOfType(domain, DOMAIN_AUTORENEW)).isEmpty(); assertBillingEventsForResource(domain, recurring); assertCursorAt(START_OF_TIME); // Cursor doesn't move on a dry run. } - @Test + @TestOfyAndSql void testSuccess_expandSingleEvent_multipleYears() throws Exception { - DateTime testTime = clock.nowUtc().plusYears(5); - clock.setTo(testTime); + clock.setTo(clock.nowUtc().plusYears(5)); List expectedEvents = new ArrayList<>(); expectedEvents.add(persistResource(recurring)); action.cursorTimeParam = Optional.of(START_OF_TIME); - runMapreduce(); + runAction(); List persistedEntries = getHistoryEntriesOfType(domain, DOMAIN_AUTORENEW, DomainHistory.class); assertThat(persistedEntries).hasSize(6); @@ -470,29 +489,25 @@ public class ExpandRecurringBillingEventsActionTest // Expecting events for '00, '01, '02, '03, '04, '05. for (int year = 0; year < 6; year++) { assertHistoryEntryMatches( - domain, - persistedEntries.get(year), - "TheRegistrar", - billingDate.plusYears(year), true); - expectedEvents.add(defaultOneTimeBuilder() - .setBillingTime(billingDate.plusYears(year)) - .setEventTime(eventDate.plusYears(year)) - .setParent(persistedEntries.get(year)) - .setSyntheticCreationTime(testTime) - .build()); + domain, persistedEntries.get(year), "TheRegistrar", billingDate.plusYears(year), true); + expectedEvents.add( + defaultOneTimeBuilder() + .setBillingTime(billingDate.plusYears(year)) + .setEventTime(eventDate.plusYears(year)) + .setParent(persistedEntries.get(year)) + .build()); } assertBillingEventsForResource(domain, Iterables.toArray(expectedEvents, BillingEvent.class)); - assertCursorAt(testTime); + assertCursorAt(currentTestTime); } - @Test + @TestOfyAndSql void testSuccess_expandSingleEvent_multipleYears_cursorInBetweenYears() throws Exception { - DateTime testTime = clock.nowUtc().plusYears(5); - clock.setTo(testTime); + clock.setTo(clock.nowUtc().plusYears(5)); List expectedEvents = new ArrayList<>(); expectedEvents.add(persistResource(recurring)); saveCursor(DateTime.parse("2003-10-02T00:00:00Z")); - runMapreduce(); + runAction(); List persistedEntries = getHistoryEntriesOfType(domain, DOMAIN_AUTORENEW, DomainHistory.class); assertThat(persistedEntries).hasSize(2); @@ -502,139 +517,153 @@ public class ExpandRecurringBillingEventsActionTest for (int year = 0; year < 2; year++) { assertHistoryEntryMatches( domain, persistedEntries.get(year), "TheRegistrar", billingDate.plusYears(year), true); - expectedEvents.add(defaultOneTimeBuilder() - .setBillingTime(billingDate.plusYears(year)) - .setParent(persistedEntries.get(year)) - .setEventTime(eventDate.plusYears(year)) - .setSyntheticCreationTime(testTime) - .build()); + expectedEvents.add( + defaultOneTimeBuilder() + .setBillingTime(billingDate.plusYears(year)) + .setParent(persistedEntries.get(year)) + .setEventTime(eventDate.plusYears(year)) + .build()); } assertBillingEventsForResource(domain, Iterables.toArray(expectedEvents, BillingEvent.class)); - assertCursorAt(testTime); + assertCursorAt(currentTestTime); } - @Test + @TestOfyAndSql void testSuccess_singleEvent_beforeRenewal() throws Exception { - DateTime testTime = DateTime.parse("2000-01-04T00:00:00Z"); - clock.setTo(testTime); + // Need to restore to the time before the clock was advanced so that the commit log's timestamp + // is not inverted when the clock is later reverted. + clock.setTo(currentTestTime); persistResource(recurring); + clock.setTo(DateTime.parse("2000-01-04T00:00:00Z")); action.cursorTimeParam = Optional.of(START_OF_TIME); - runMapreduce(); + runAction(); // No new history entries should be generated assertThat(getHistoryEntriesOfType(domain, DOMAIN_AUTORENEW)).isEmpty(); assertBillingEventsForResource(domain, recurring); - assertCursorAt(testTime); + assertCursorAt(currentTestTime); } - @Test + @TestOfyAndSql void testSuccess_singleEvent_afterRecurrenceEnd_inAutorenewGracePeriod() throws Exception { // The domain creation date is 1999-01-05, and the first renewal date is thus 2000-01-05. - DateTime testTime = DateTime.parse("2001-02-06T00:00:00Z"); - clock.setTo(testTime); - recurring = persistResource(recurring.asBuilder() - // The domain deletion date is 2000-01-29, which is within the 45 day autorenew grace period - // from the renewal date. - .setRecurrenceEndTime(DateTime.parse("2000-01-29T00:00:00Z")) - .setEventTime(domain.getCreationTime().plusYears(1)) - .build()); + clock.setTo(DateTime.parse("2001-02-06T00:00:00Z")); + recurring = + persistResource( + recurring + .asBuilder() + // The domain deletion date is 2000-01-29, which is within the 45 day autorenew + // grace period + // from the renewal date. + .setRecurrenceEndTime(DateTime.parse("2000-01-29T00:00:00Z")) + .setEventTime(domain.getCreationTime().plusYears(1)) + .build()); action.cursorTimeParam = Optional.of(START_OF_TIME); - runMapreduce(); + runAction(); DomainHistory persistedEntry = getOnlyHistoryEntryOfType(domain, DOMAIN_AUTORENEW, DomainHistory.class); assertHistoryEntryMatches( domain, persistedEntry, "TheRegistrar", DateTime.parse("2000-02-19T00:00:00Z"), false); - BillingEvent.OneTime expected = defaultOneTimeBuilder() - .setBillingTime(DateTime.parse("2000-02-19T00:00:00Z")) - .setParent(persistedEntry) - .setSyntheticCreationTime(testTime) - .build(); + BillingEvent.OneTime expected = + defaultOneTimeBuilder() + .setBillingTime(DateTime.parse("2000-02-19T00:00:00Z")) + .setParent(persistedEntry) + .build(); assertBillingEventsForResource(domain, recurring, expected); - assertCursorAt(testTime); + assertCursorAt(currentTestTime); } - @Test + @TestOfyAndSql void testSuccess_singleEvent_afterRecurrenceEnd_outsideAutorenewGracePeriod() throws Exception { // The domain creation date is 1999-01-05, and the first renewal date is thus 2000-01-05. - DateTime testTime = DateTime.parse("2001-02-06T00:00:00Z"); - clock.setTo(testTime); - recurring = persistResource(recurring.asBuilder() - // The domain deletion date is 2000-04-05, which is not within the 45 day autorenew grace - // period from the renewal date. - .setRecurrenceEndTime(DateTime.parse("2000-04-05T00:00:00Z")) - .setEventTime(domain.getCreationTime().plusYears(1)) - .build()); + clock.setTo(DateTime.parse("2001-02-06T00:00:00Z")); + recurring = + persistResource( + recurring + .asBuilder() + // The domain deletion date is 2000-04-05, which is not within the 45 day autorenew + // grace + // period from the renewal date. + .setRecurrenceEndTime(DateTime.parse("2000-04-05T00:00:00Z")) + .setEventTime(domain.getCreationTime().plusYears(1)) + .build()); action.cursorTimeParam = Optional.of(START_OF_TIME); - runMapreduce(); + runAction(); DomainHistory persistedEntry = getOnlyHistoryEntryOfType(domain, DOMAIN_AUTORENEW, DomainHistory.class); assertHistoryEntryMatches( domain, persistedEntry, "TheRegistrar", DateTime.parse("2000-02-19T00:00:00Z"), true); - BillingEvent.OneTime expected = defaultOneTimeBuilder() - .setBillingTime(DateTime.parse("2000-02-19T00:00:00Z")) - .setParent(persistedEntry) - .setSyntheticCreationTime(testTime) - .build(); + BillingEvent.OneTime expected = + defaultOneTimeBuilder() + .setBillingTime(DateTime.parse("2000-02-19T00:00:00Z")) + .setParent(persistedEntry) + .build(); assertBillingEventsForResource(domain, recurring, expected); - assertCursorAt(testTime); + assertCursorAt(currentTestTime); } - @Test + @TestOfyAndSql void testSuccess_expandSingleEvent_billingTimeOnLeapYear() throws Exception { recurring = persistResource( recurring.asBuilder().setEventTime(DateTime.parse("2000-01-15T00:00:00Z")).build()); action.cursorTimeParam = Optional.of(START_OF_TIME); - runMapreduce(); + runAction(); DomainHistory persistedEntry = getOnlyHistoryEntryOfType(domain, DOMAIN_AUTORENEW, DomainHistory.class); assertHistoryEntryMatches( domain, persistedEntry, "TheRegistrar", DateTime.parse("2000-02-29T00:00:00Z"), true); - BillingEvent.OneTime expected = defaultOneTimeBuilder() - .setBillingTime(DateTime.parse("2000-02-29T00:00:00Z")) - .setEventTime(DateTime.parse("2000-01-15T00:00:00Z")) - .setParent(persistedEntry) - .build(); + BillingEvent.OneTime expected = + defaultOneTimeBuilder() + .setBillingTime(DateTime.parse("2000-02-29T00:00:00Z")) + .setEventTime(DateTime.parse("2000-01-15T00:00:00Z")) + .setParent(persistedEntry) + .build(); assertBillingEventsForResource(domain, expected, recurring); - assertCursorAt(beginningOfTest); + assertCursorAt(currentTestTime); } - @Test + @TestOfyAndSql void testSuccess_expandSingleEvent_billingTimeNotOnLeapYear() throws Exception { - DateTime testTime = DateTime.parse("2001-12-01T00:00:00Z"); recurring = persistResource( recurring.asBuilder().setEventTime(DateTime.parse("2001-01-15T00:00:00Z")).build()); action.cursorTimeParam = Optional.of(START_OF_TIME); - clock.setTo(testTime); - runMapreduce(); + clock.setTo(DateTime.parse("2001-12-01T00:00:00Z")); + runAction(); DomainHistory persistedEntry = getOnlyHistoryEntryOfType(domain, DOMAIN_AUTORENEW, DomainHistory.class); assertHistoryEntryMatches( domain, persistedEntry, "TheRegistrar", DateTime.parse("2001-03-01T00:00:00Z"), true); - BillingEvent.OneTime expected = defaultOneTimeBuilder() - .setBillingTime(DateTime.parse("2001-03-01T00:00:00Z")) - .setEventTime(DateTime.parse("2001-01-15T00:00:00Z")) - .setParent(persistedEntry) - .setSyntheticCreationTime(testTime) - .build(); + BillingEvent.OneTime expected = + defaultOneTimeBuilder() + .setBillingTime(DateTime.parse("2001-03-01T00:00:00Z")) + .setEventTime(DateTime.parse("2001-01-15T00:00:00Z")) + .setParent(persistedEntry) + .build(); assertBillingEventsForResource(domain, expected, recurring); - assertCursorAt(testTime); + assertCursorAt(currentTestTime); } - @Test + @TestSqlOnly void testSuccess_expandMultipleEvents() throws Exception { persistResource(recurring); - BillingEvent.Recurring recurring2 = persistResource(recurring.asBuilder() - .setEventTime(recurring.getEventTime().plusMonths(3)) - .setId(3L) - .build()); + BillingEvent.Recurring recurring2 = + persistResource( + recurring + .asBuilder() + .setEventTime(recurring.getEventTime().plusMonths(3)) + .setId(3L) + .build()); action.cursorTimeParam = Optional.of(START_OF_TIME); - runMapreduce(); + runAction(); List persistedEntries = getHistoryEntriesOfType(domain, DOMAIN_AUTORENEW, DomainHistory.class); assertThat(persistedEntries).hasSize(2); assertHistoryEntryMatches( - domain, persistedEntries.get(0), "TheRegistrar", DateTime.parse("2000-02-19T00:00:00Z"), + domain, + persistedEntries.get(0), + "TheRegistrar", + DateTime.parse("2000-02-19T00:00:00Z"), true); BillingEvent.OneTime expected = defaultOneTimeBuilder() @@ -642,7 +671,10 @@ public class ExpandRecurringBillingEventsActionTest .setCancellationMatchingBillingEvent(recurring.createVKey()) .build(); assertHistoryEntryMatches( - domain, persistedEntries.get(1), "TheRegistrar", DateTime.parse("2000-05-20T00:00:00Z"), + domain, + persistedEntries.get(1), + "TheRegistrar", + DateTime.parse("2000-05-20T00:00:00Z"), true); BillingEvent.OneTime expected2 = defaultOneTimeBuilder() @@ -652,10 +684,10 @@ public class ExpandRecurringBillingEventsActionTest .setCancellationMatchingBillingEvent(recurring2.createVKey()) .build(); assertBillingEventsForResource(domain, expected, expected2, recurring, recurring2); - assertCursorAt(beginningOfTest); + assertCursorAt(currentTestTime); } - @Test + @TestOfyAndSql void testSuccess_premiumDomain() throws Exception { persistResource( Registry.get("tld") @@ -664,86 +696,87 @@ public class ExpandRecurringBillingEventsActionTest .build()); persistResource(recurring); action.cursorTimeParam = Optional.of(START_OF_TIME); - runMapreduce(); + runAction(); DomainHistory persistedEntry = getOnlyHistoryEntryOfType(domain, DOMAIN_AUTORENEW, DomainHistory.class); assertHistoryEntryMatches( domain, persistedEntry, "TheRegistrar", DateTime.parse("2000-02-19T00:00:00Z"), true); - BillingEvent.OneTime expected = defaultOneTimeBuilder() - .setParent(persistedEntry) - .setCost(Money.of(USD, 100)) - .build(); + BillingEvent.OneTime expected = + defaultOneTimeBuilder().setParent(persistedEntry).setCost(Money.of(USD, 100)).build(); assertBillingEventsForResource(domain, expected, recurring); - assertCursorAt(beginningOfTest); + assertCursorAt(currentTestTime); } - @Test + @TestOfyAndSql void testSuccess_varyingRenewPrices() throws Exception { - DateTime testTime = beginningOfTest.plusYears(1); + clock.setTo(currentTestTime); persistResource( Registry.get("tld") .asBuilder() .setRenewBillingCostTransitions( ImmutableSortedMap.of( - START_OF_TIME, Money.of(USD, 8), - DateTime.parse("2000-06-01T00:00:00Z"), Money.of(USD, 10))) + START_OF_TIME, + Money.of(USD, 8), + DateTime.parse("2000-06-01T00:00:00Z"), + Money.of(USD, 10))) .build()); - clock.setTo(testTime); + clock.setTo(DateTime.parse("2001-10-02T00:00:00Z")); persistResource(recurring); action.cursorTimeParam = Optional.of(START_OF_TIME); - runMapreduce(); + runAction(); List persistedEntries = getHistoryEntriesOfType(domain, DOMAIN_AUTORENEW, DomainHistory.class); assertThat(persistedEntries).hasSize(2); DateTime eventDate = DateTime.parse("2000-01-05T00:00:00Z"); DateTime billingDate = DateTime.parse("2000-02-19T00:00:00Z"); assertHistoryEntryMatches(domain, persistedEntries.get(0), "TheRegistrar", billingDate, true); - BillingEvent.OneTime cheaper = defaultOneTimeBuilder() - .setBillingTime(billingDate) - .setEventTime(eventDate) - .setParent(persistedEntries.get(0)) - .setCost(Money.of(USD, 8)) - .setSyntheticCreationTime(testTime) - .build(); + BillingEvent.OneTime cheaper = + defaultOneTimeBuilder() + .setBillingTime(billingDate) + .setEventTime(eventDate) + .setParent(persistedEntries.get(0)) + .setCost(Money.of(USD, 8)) + .build(); assertHistoryEntryMatches( domain, persistedEntries.get(1), "TheRegistrar", billingDate.plusYears(1), true); - BillingEvent.OneTime expensive = cheaper.asBuilder() - .setCost(Money.of(USD, 10)) - .setBillingTime(billingDate.plusYears(1)) - .setEventTime(eventDate.plusYears(1)) - .setParent(persistedEntries.get(1)) - .build(); + BillingEvent.OneTime expensive = + cheaper + .asBuilder() + .setCost(Money.of(USD, 10)) + .setBillingTime(billingDate.plusYears(1)) + .setEventTime(eventDate.plusYears(1)) + .setParent(persistedEntries.get(1)) + .build(); assertBillingEventsForResource(domain, recurring, cheaper, expensive); - assertCursorAt(testTime); + assertCursorAt(currentTestTime); } - @Test + @TestOfyAndSql void testFailure_cursorAfterExecutionTime() { action.cursorTimeParam = Optional.of(clock.nowUtc().plusYears(1)); - IllegalArgumentException thrown = - assertThrows(IllegalArgumentException.class, this::runMapreduce); + IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, this::runAction); assertThat(thrown) .hasMessageThat() .contains("Cursor time must be earlier than execution time."); } - @Test + @TestOfyAndSql void testFailure_cursorAtExecutionTime() { // The clock advances one milli on runMapreduce. action.cursorTimeParam = Optional.of(clock.nowUtc().plusMillis(1)); - IllegalArgumentException thrown = - assertThrows(IllegalArgumentException.class, this::runMapreduce); + IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, this::runAction); assertThat(thrown) .hasMessageThat() .contains("Cursor time must be earlier than execution time."); } - @Test + @TestOfyOnly void testFailure_mapperException_doesNotMoveCursor() throws Exception { saveCursor(START_OF_TIME); // Need a saved cursor to verify that it didn't move. + clock.advanceOneMilli(); // Set target to a TLD that doesn't exist. recurring = persistResource(recurring.asBuilder().setTargetId("domain.junk").build()); - runMapreduce(); + runAction(); // No new history entries should be generated assertThat(getHistoryEntriesOfType(domain, DOMAIN_AUTORENEW)).isEmpty(); assertBillingEvents(recurring); // only the bogus one in Datastore diff --git a/db/src/main/resources/sql/er_diagram/brief_er_diagram.html b/db/src/main/resources/sql/er_diagram/brief_er_diagram.html index 6faf42d98..4bea1d77f 100644 --- a/db/src/main/resources/sql/er_diagram/brief_er_diagram.html +++ b/db/src/main/resources/sql/er_diagram/brief_er_diagram.html @@ -261,2791 +261,2825 @@ td.section { generated on - 2021-05-19 19:40:03.535289 + 2021-06-02 13:15:04.600002 last flyway file - V96__rename_sql_checkpoint_fields.sql + V97__add_recurrence_history_id_column_to_onetime.sql

 

 

- + SchemaCrawler_Diagram - - + + generated by - + SchemaCrawler 16.10.1 - + generated on - - 2021-05-19 19:40:03.535289 + + 2021-06-02 13:15:04.600002 - + allocationtoken_a08ccbef - - + + public.AllocationToken - - + + [table] - + token - + - + text not null - + domain_name - + - + text - + billingevent_a57d1815 - - + + public.BillingEvent - - + + [table] - + billing_event_id - + - + int8 not null - + registrar_id - + - + text not null - + domain_history_revision_id - + - + int8 not null - + domain_repo_id - + - + text not null - + event_time - + - + timestamptz not null - + allocation_token - + - + text - + billing_time - + - + timestamptz - + cancellation_matching_billing_recurrence_id - + - + int8 - + synthetic_creation_time - + - + timestamptz - + + recurrence_history_revision_id + + + + + int8 + + billingevent_a57d1815:w->allocationtoken_a08ccbef:e - - - - - - - - + + + + + + + + fk_billing_event_allocation_token billingrecurrence_5fa2cb01 - - + + public.BillingRecurrence - - + + [table] - + billing_recurrence_id - + - + int8 not null - + registrar_id - + - + text not null - + domain_history_revision_id - + - + int8 not null - + domain_repo_id - + - + text not null - + event_time - + - + timestamptz not null - + recurrence_end_time - + - + timestamptz - + recurrence_time_of_year - + - + text - + billingevent_a57d1815:w->billingrecurrence_5fa2cb01:e - - - - - - - - + + + + + + + + fk_billing_event_cancellation_matching_billing_recurrence_id domainhistory_a54cc226 - - + + public.DomainHistory - - + + [table] - + history_revision_id - + - + int8 not null - + history_registrar_id - + - + text - + history_modification_time - + - + timestamptz not null - + history_type - + - + text not null - + creation_time - + - + timestamptz - + domain_repo_id - + - + text not null - + billingevent_a57d1815:w->domainhistory_a54cc226:e - - - - - - - - + + + + + + + + fk_billing_event_domain_history billingevent_a57d1815:w->domainhistory_a54cc226:e - - - - - - - - + + + + + + + + fk_billing_event_domain_history + + + billingevent_a57d1815:w->domainhistory_a54cc226:e + + + + + + + + + fk_billing_event_recurrence_history + + + + billingevent_a57d1815:w->domainhistory_a54cc226:e + + + + + + + + + fk_billing_event_recurrence_history + registrar_6e1503e3 - - + + public.Registrar - - + + [table] - + registrar_id - + - + text not null - + iana_identifier - + - + int8 - + registrar_name - + - + text not null - + - + billingevent_a57d1815:w->registrar_6e1503e3:e - - - - - - - - + + + + + + + + fk_billing_event_registrar_id billingcancellation_6eedf614 - - + + public.BillingCancellation - - + + [table] - + billing_cancellation_id - + - + int8 not null - + registrar_id - + - + text not null - + domain_history_revision_id - + - + int8 not null - + domain_repo_id - + - + text not null - + event_time - + - + timestamptz not null - + billing_time - + - + timestamptz - + billing_event_id - + - + int8 - + billing_recurrence_id - + - + int8 - + billingcancellation_6eedf614:w->billingevent_a57d1815:e - - - - - - - - + + + + + + + + fk_billing_cancellation_billing_event_id billingcancellation_6eedf614:w->billingrecurrence_5fa2cb01:e - - - - - - - - + + + + + + + + fk_billing_cancellation_billing_recurrence_id billingcancellation_6eedf614:w->domainhistory_a54cc226:e - - - - - - - - + + + + + + + + fk_billing_cancellation_domain_history billingcancellation_6eedf614:w->domainhistory_a54cc226:e - - - - - - - - + + + + + + + + fk_billing_cancellation_domain_history - + billingcancellation_6eedf614:w->registrar_6e1503e3:e - - - - - - - - + + + + + + + + fk_billing_cancellation_registrar_id domain_6c51cffa - - + + public.Domain - - + + [table] - + repo_id - + - + text not null - + creation_registrar_id - + - + text not null - + creation_time - + - + timestamptz not null - + current_sponsor_registrar_id - + - + text not null - + deletion_time - + - + timestamptz - + last_epp_update_registrar_id - + - + text - + domain_name - + - + text - + tld - + - + text - + admin_contact - + - + text - + billing_contact - + - + text - + registrant_contact - + - + text - + tech_contact - + - + text - + transfer_billing_cancellation_id - + - + int8 - + transfer_billing_event_id - + - + int8 - + transfer_billing_recurrence_id - + - + int8 - + transfer_gaining_registrar_id - + - + text - + transfer_losing_registrar_id - + - + text - + billing_recurrence_id - + - + int8 - + autorenew_end_time - + - + timestamptz - + domain_6c51cffa:w->billingevent_a57d1815:e - - - - - - - - + + + + + + + + fk_domain_transfer_billing_event_id domain_6c51cffa:w->billingcancellation_6eedf614:e - - - - - - - - + + + + + + + + fk_domain_transfer_billing_cancellation_id domain_6c51cffa:w->billingrecurrence_5fa2cb01:e - - - - - - - - + + + + + + + + fk_domain_billing_recurrence_id domain_6c51cffa:w->billingrecurrence_5fa2cb01:e - - - - - - - - + + + + + + + + fk_domain_transfer_billing_recurrence_id contact_8de8cb16 - - + + public.Contact - - + + [table] - + repo_id - + - + text not null - + creation_registrar_id - + - + text not null - + creation_time - + - + timestamptz not null - + current_sponsor_registrar_id - + - + text not null - + deletion_time - + - + timestamptz - + last_epp_update_registrar_id - + - + text - + contact_id - + - + text - + search_name - + - + text - + transfer_gaining_registrar_id - + - + text - + transfer_losing_registrar_id - + - + text - + domain_6c51cffa:w->contact_8de8cb16:e - - - - - - - - + + + + + + + + fk_domain_admin_contact domain_6c51cffa:w->contact_8de8cb16:e - - - - - - - - + + + + + + + + fk_domain_billing_contact domain_6c51cffa:w->contact_8de8cb16:e - - - - - - - - + + + + + + + + fk_domain_registrant_contact domain_6c51cffa:w->contact_8de8cb16:e - - - - - - - - + + + + + + + + fk_domain_tech_contact - - domain_6c51cffa:w->registrar_6e1503e3:e - - - - - - - - - fk2jc69qyg2tv9hhnmif6oa1cx1 - - - - domain_6c51cffa:w->registrar_6e1503e3:e - - - - - - - - - fk2u3srsfbei272093m3b3xwj23 - - domain_6c51cffa:w->registrar_6e1503e3:e - - - - - - - - - fkjc0r9r5y1lfbt4gpbqw4wsuvq + + + + + + + + + fk2jc69qyg2tv9hhnmif6oa1cx1 domain_6c51cffa:w->registrar_6e1503e3:e - - - - - - - - - fk_domain_transfer_gaining_registrar_id + + + + + + + + + fk2u3srsfbei272093m3b3xwj23 domain_6c51cffa:w->registrar_6e1503e3:e - - - - - - - - + + + + + + + + + fkjc0r9r5y1lfbt4gpbqw4wsuvq + + + + domain_6c51cffa:w->registrar_6e1503e3:e + + + + + + + + + fk_domain_transfer_gaining_registrar_id + + + + domain_6c51cffa:w->registrar_6e1503e3:e + + + + + + + + fk_domain_transfer_losing_registrar_id tld_f1fa57e2 - - + + public.Tld - - + + [table] - + tld_name - + - + text not null - + - + domain_6c51cffa:w->tld_f1fa57e2:e - - - - - - - - + + + + + + + + fk_domain_tld graceperiod_cd3b2e8f - - + + public.GracePeriod - - + + [table] - + grace_period_id - + - + int8 not null - + billing_event_id - + - + int8 - + billing_recurrence_id - + - + int8 - + registrar_id - + - + text not null - + domain_repo_id - + - + text not null - + graceperiod_cd3b2e8f:w->billingevent_a57d1815:e - - - - - - - - + + + + + + + + fk_grace_period_billing_event_id graceperiod_cd3b2e8f:w->domain_6c51cffa:e - - - - - - - - + + + + + + + + fk_grace_period_domain_repo_id graceperiod_cd3b2e8f:w->billingrecurrence_5fa2cb01:e - - - - - - - - + + + + + + + + fk_grace_period_billing_recurrence_id - + graceperiod_cd3b2e8f:w->registrar_6e1503e3:e - - - - - - - - + + + + + + + + fk_grace_period_registrar_id - + billingrecurrence_5fa2cb01:w->domainhistory_a54cc226:e - - - - - - - - + + + + + + + + fk_billing_recurrence_domain_history - + billingrecurrence_5fa2cb01:w->domainhistory_a54cc226:e - - - - - - - - + + + + + + + + fk_billing_recurrence_domain_history - + billingrecurrence_5fa2cb01:w->registrar_6e1503e3:e - - - - - - - - + + + + + + + + fk_billing_recurrence_registrar_id claimsentry_105da9f1 - - + + public.ClaimsEntry - - + + [table] - + revision_id - + - + int8 not null - + domain_label - + - + text not null - + claimslist_3d49bc2b - - + + public.ClaimsList - - + + [table] - + revision_id - + - + bigserial not null - + - + auto-incremented - + claimsentry_105da9f1:w->claimslist_3d49bc2b:e - - - - - - - - + + + + + + + + fk6sc6at5hedffc0nhdcab6ivuq - - contact_8de8cb16:w->registrar_6e1503e3:e - - - - - - - - - fk1sfyj7o7954prbn1exk7lpnoe - - - - contact_8de8cb16:w->registrar_6e1503e3:e - - - - - - - - - fk93c185fx7chn68uv7nl6uv2s0 - - contact_8de8cb16:w->registrar_6e1503e3:e - - - - - - - - - fkmb7tdiv85863134w1wogtxrb2 + + + + + + + + + fk1sfyj7o7954prbn1exk7lpnoe contact_8de8cb16:w->registrar_6e1503e3:e - - - - - - - - - fk_contact_transfer_gaining_registrar_id + + + + + + + + + fk93c185fx7chn68uv7nl6uv2s0 contact_8de8cb16:w->registrar_6e1503e3:e - - - - - - - - + + + + + + + + + fkmb7tdiv85863134w1wogtxrb2 + + + + contact_8de8cb16:w->registrar_6e1503e3:e + + + + + + + + + fk_contact_transfer_gaining_registrar_id + + + + contact_8de8cb16:w->registrar_6e1503e3:e + + + + + + + + fk_contact_transfer_losing_registrar_id contacthistory_d2964f8a - - + + public.ContactHistory - - + + [table] - + history_revision_id - + - + int8 not null - + history_registrar_id - + - + text - + history_modification_time - + - + timestamptz not null - + history_type - + - + text not null - + creation_time - + - + timestamptz - + contact_repo_id - + - + text not null - + contacthistory_d2964f8a:w->contact_8de8cb16:e - - - - - - - - + + + + + + + + fk_contact_history_contact_repo_id - + contacthistory_d2964f8a:w->registrar_6e1503e3:e - - - - - - - - + + + + + + + + fk_contact_history_registrar_id pollmessage_614a523e - - + + public.PollMessage - - + + [table] - + poll_message_id - + - + int8 not null - + registrar_id - + - + text not null - + contact_repo_id - + - + text - + contact_history_revision_id - + - + int8 - + domain_repo_id - + - + text - + domain_history_revision_id - + - + int8 - + event_time - + - + timestamptz not null - + host_repo_id - + - + text - + host_history_revision_id - + - + int8 - + transfer_response_gaining_registrar_id - + - + text - + transfer_response_losing_registrar_id - + - + text - + pollmessage_614a523e:w->domain_6c51cffa:e - - - - - - - - + + + + + + + + fk_poll_message_domain_repo_id pollmessage_614a523e:w->contact_8de8cb16:e - - - - - - - - + + + + + + + + fk_poll_message_contact_repo_id pollmessage_614a523e:w->contacthistory_d2964f8a:e - - - - - - - - + + + + + + + + fk_poll_message_contact_history pollmessage_614a523e:w->contacthistory_d2964f8a:e - - - - - - - - + + + + + + + + fk_poll_message_contact_history - + pollmessage_614a523e:w->domainhistory_a54cc226:e - - - - - - - - + + + + + + + + fk_poll_message_domain_history - + pollmessage_614a523e:w->domainhistory_a54cc226:e - - - - - - - - + + + + + + + + fk_poll_message_domain_history host_f21b78de - - + + public.Host - - + + [table] - + repo_id - + - + text not null - + creation_registrar_id - + - + text - + current_sponsor_registrar_id - + - + text - + last_epp_update_registrar_id - + - + text - + superordinate_domain - + - + text - + - + pollmessage_614a523e:w->host_f21b78de:e - - - - - - - - + + + + + + + + fk_poll_message_host_repo_id hosthistory_56210c2 - - + + public.HostHistory - - + + [table] - + history_revision_id - + - + int8 not null - + history_registrar_id - + - + text not null - + history_modification_time - + - + timestamptz not null - + history_type - + - + text not null - + host_name - + - + text - + creation_time - + - + timestamptz - + host_repo_id - + - + text not null - + - + pollmessage_614a523e:w->hosthistory_56210c2:e - - - - - - - - + + + + + + + + fk_poll_message_host_history - + pollmessage_614a523e:w->hosthistory_56210c2:e - - - - - - - - + + + + + + + + fk_poll_message_host_history - - pollmessage_614a523e:w->registrar_6e1503e3:e - - - - - - - - - fk_poll_message_registrar_id - - - - pollmessage_614a523e:w->registrar_6e1503e3:e - - - - - - - - - fk_poll_message_transfer_response_gaining_registrar_id - - pollmessage_614a523e:w->registrar_6e1503e3:e - - - - - - - - + + + + + + + + + fk_poll_message_registrar_id + + + + pollmessage_614a523e:w->registrar_6e1503e3:e + + + + + + + + + fk_poll_message_transfer_response_gaining_registrar_id + + + + pollmessage_614a523e:w->registrar_6e1503e3:e + + + + + + + + fk_poll_message_transfer_response_losing_registrar_id cursor_6af40e8c - - + + public."Cursor" - - + + [table] - + "scope" - + - + text not null - + type - + - + text not null - + delegationsignerdata_e542a872 - - + + public.DelegationSignerData - - + + [table] - + domain_repo_id - + - + text not null - + key_tag - + - + int4 not null - + algorithm - + - + int4 not null - + digest - + - + bytea not null - + digest_type - + - + int4 not null - + delegationsignerdata_e542a872:w->domain_6c51cffa:e - - - - - - - - + + + + + + + + fktr24j9v14ph2mfuw2gsmt12kq domainhistory_a54cc226:w->domain_6c51cffa:e - - - - - - - - + + + + + + + + fk_domain_history_domain_repo_id - + domainhistory_a54cc226:w->registrar_6e1503e3:e - - - - - - - - + + + + + + + + fk_domain_history_registrar_id domainhost_1ea127c2 - - + + public.DomainHost - - + + [table] - + domain_repo_id - + - + text not null - + host_repo_id - + - + text - + domainhost_1ea127c2:w->domain_6c51cffa:e - - - - - - - - + + + + + + + + fkfmi7bdink53swivs390m2btxg - + domainhost_1ea127c2:w->host_f21b78de:e - - - - - - - - + + + + + + + + fk_domainhost_host_valid host_f21b78de:w->domain_6c51cffa:e - - - - - - - - + + + + + + + + fk_host_superordinate_domain - - host_f21b78de:w->registrar_6e1503e3:e - - - - - - - - - fk_host_creation_registrar_id - - - - host_f21b78de:w->registrar_6e1503e3:e - - - - - - - - - fk_host_current_sponsor_registrar_id - - host_f21b78de:w->registrar_6e1503e3:e - - - - - - - - + + + + + + + + + fk_host_creation_registrar_id + + + + host_f21b78de:w->registrar_6e1503e3:e + + + + + + + + + fk_host_current_sponsor_registrar_id + + + + host_f21b78de:w->registrar_6e1503e3:e + + + + + + + + fk_host_last_epp_update_registrar_id domaindsdatahistory_995b060d - - + + public.DomainDsDataHistory - - + + [table] - + ds_data_history_revision_id - + - + int8 not null - + domain_history_revision_id - + - + int8 not null - + domain_repo_id - + - + text - + - + domaindsdatahistory_995b060d:w->domainhistory_a54cc226:e - - - - - - - - + + + + + + + + fko4ilgyyfnvppbpuivus565i0j - + domaindsdatahistory_995b060d:w->domainhistory_a54cc226:e - - - - - - - - + + + + + + + + fko4ilgyyfnvppbpuivus565i0j domainhistoryhost_9f3f23ee - - + + public.DomainHistoryHost - - + + [table] - + domain_history_history_revision_id - + - + int8 not null - + host_repo_id - + - + text - + domain_history_domain_repo_id - + - + text not null - + - + domainhistoryhost_9f3f23ee:w->domainhistory_a54cc226:e - - - - - - - - + + + + + + + + fka9woh3hu8gx5x0vly6bai327n - + domainhistoryhost_9f3f23ee:w->domainhistory_a54cc226:e - - - - - - - - + + + + + + + + fka9woh3hu8gx5x0vly6bai327n domaintransactionrecord_6e77ff61 - - + + public.DomainTransactionRecord - - + + [table] - + id - + - + bigserial not null - + - + auto-incremented - + tld - + - + text not null - + domain_repo_id - + - + text - + history_revision_id - + - + int8 - + - + domaintransactionrecord_6e77ff61:w->domainhistory_a54cc226:e - - - - - - - - + + + + + + + + fkcjqe54u72kha71vkibvxhjye7 - + domaintransactionrecord_6e77ff61:w->domainhistory_a54cc226:e - - - - - - - - + + + + + + + + fkcjqe54u72kha71vkibvxhjye7 - + domaintransactionrecord_6e77ff61:w->tld_f1fa57e2:e - - - - - - - - + + + + + + + + fk_domain_transaction_record_tld graceperiodhistory_40ccc1f1 - - + + public.GracePeriodHistory - - + + [table] - + grace_period_history_revision_id - + - + int8 not null - + domain_repo_id - + - + text not null - + domain_history_revision_id - + - + int8 - + - + graceperiodhistory_40ccc1f1:w->domainhistory_a54cc226:e - - - - - - - - + + + + + + + + fk7w3cx8d55q8bln80e716tr7b8 - + graceperiodhistory_40ccc1f1:w->domainhistory_a54cc226:e - - - - - - - - + + + + + + + + fk7w3cx8d55q8bln80e716tr7b8 - + hosthistory_56210c2:w->host_f21b78de:e - - - - - - - - + + + + + + + + fk_hosthistory_host - + hosthistory_56210c2:w->registrar_6e1503e3:e - - - - - - - - + + + + + + + + fk_history_registrar_id kmssecret_f3b28857 - - + + public.KmsSecret - - + + [table] - + revision_id - + - + int8 not null - + secret_name - + - + text not null - + lock_f21d4861 - - + + public.Lock - - + + [table] - + resource_name - + - + text not null - + "scope" - + - + text not null - + premiumentry_b0060b91 - - + + public.PremiumEntry - - + + [table] - + revision_id - + - + int8 not null - + domain_label - + - + text not null - + premiumlist_7c3ea68b - - + + public.PremiumList - - + + [table] - + revision_id - + - + bigserial not null - + - + auto-incremented - + name - + - + text not null - + - + premiumentry_b0060b91:w->premiumlist_7c3ea68b:e - - - - - - - - + + + + + + + + fko0gw90lpo1tuee56l0nb6y6g5 rderevision_83396864 - - + + public.RdeRevision - - + + [table] - + tld - + - + text not null - + mode - + - + text not null - + "date" - + - + date not null - + registrarpoc_ab47054d - - + + public.RegistrarPoc - - + + [table] - + email_address - + - + text not null - + gae_user_id - + - + text - + registrar_id - + - + text not null - + - + registrarpoc_ab47054d:w->registrar_6e1503e3:e - - - - - - - - + + + + + + + + fk_registrar_poc_registrar_id registrylock_ac88663e - - + + public.RegistryLock - - + + [table] - + revision_id - + - + bigserial not null - + - + auto-incremented - + registrar_id - + - + text not null - + repo_id - + - + text not null - + verification_code - + - + text not null - + relock_revision_id - + - + int8 - + - + registrylock_ac88663e:w->registrylock_ac88663e:e - - - - - - - - + + + + + + + + fk2lhcwpxlnqijr96irylrh1707 reservedentry_1a7b8520 - - + + public.ReservedEntry - - + + [table] - + revision_id - + - + int8 not null - + domain_label - + - + text not null - + reservedlist_b97c3f1c - - + + public.ReservedList - - + + [table] - + revision_id - + - + bigserial not null - + - + auto-incremented - + name - + - + text not null - + - + reservedentry_1a7b8520:w->reservedlist_b97c3f1c:e - - - - - - - - + + + + + + + + fkgq03rk0bt1hb915dnyvd3vnfc serversecret_6cc90f09 - - + + public.ServerSecret - - + + [table] - + id - + - + int8 not null - + signedmarkrevocationentry_99c39721 - - + + public.SignedMarkRevocationEntry - - + + [table] - + revision_id - + - + int8 not null - + smd_id - + - + text not null - + signedmarkrevocationlist_c5d968fb - - + + public.SignedMarkRevocationList - - + + [table] - + revision_id - + - + bigserial not null - + - + auto-incremented - + - + signedmarkrevocationentry_99c39721:w->signedmarkrevocationlist_c5d968fb:e - - - - - - - - + + + + + + + + fk5ivlhvs3121yx2li5tqh54u4 spec11threatmatch_a61228a6 - - + + public.Spec11ThreatMatch - - + + [table] - + id - + - + bigserial not null - + - + auto-incremented - + check_date - + - + date not null - + registrar_id - + - + text not null - + tld - + - + text not null - + sqlreplaycheckpoint_342081b3 - - + + public.SqlReplayCheckpoint - - + + [table] - + id - + - + int8 not null - + tmchcrl_d282355 - - + + public.TmchCrl - - + + [table] - + id - + - + int8 not null - + transaction_d50389d4 - - + + public.Transaction - - + + [table] - + id - + - + bigserial not null - + - + auto-incremented - + @@ -3288,6 +3322,11 @@ td.section { synthetic_creation_time timestamptz +
+ + recurrence_history_revision_id + int8 +
@@ -3401,6 +3440,23 @@ td.section { domain_history_revision_id (0..many)→ public.DomainHistory.history_revision_id +
+ +
+
+ fk_billing_event_recurrence_history + [foreign key, with no action] +
+
+ + domain_repo_id (0..many)→ public.DomainHistory.domain_repo_id + +
+
+ + recurrence_history_revision_id (0..many)→ public.DomainHistory.history_revision_id + +

 

@@ -4671,6 +4727,23 @@ td.section { + + fk_billing_event_recurrence_history + [foreign key, with no action] + + + + domain_repo_id ←(0..many) public.BillingEvent.domain_repo_id + + + + + history_revision_id ←(0..many) public.BillingEvent.recurrence_history_revision_id + + + + + fk_billing_recurrence_domain_history [foreign key, with no action] diff --git a/db/src/main/resources/sql/er_diagram/full_er_diagram.html b/db/src/main/resources/sql/er_diagram/full_er_diagram.html index 3a2fc49e3..e778fbe8b 100644 --- a/db/src/main/resources/sql/er_diagram/full_er_diagram.html +++ b/db/src/main/resources/sql/er_diagram/full_er_diagram.html @@ -261,6263 +261,6297 @@ td.section { generated on - 2021-05-19 19:40:01.33368 + 2021-06-02 13:14:59.432102 last flyway file - V96__rename_sql_checkpoint_fields.sql + V97__add_recurrence_history_id_column_to_onetime.sql

 

 

- + SchemaCrawler_Diagram - - + + generated by - + SchemaCrawler 16.10.1 - + generated on - - 2021-05-19 19:40:01.33368 + + 2021-06-02 13:14:59.432102 - + allocationtoken_a08ccbef - - + + public.AllocationToken - - + + [table] - + token - + - + text not null - + update_timestamp - + - + timestamptz - + allowed_registrar_ids - + - + _text - + allowed_tlds - + - + _text - + creation_time - + - + timestamptz not null - + discount_fraction - + - + float8(17, 17) not null - + discount_premiums - + - + bool not null - + discount_years - + - + int4 not null - + domain_name - + - + text - + redemption_domain_repo_id - + - + text - + token_status_transitions - + - + "hstore" - + token_type - + - + text - + redemption_domain_history_id - + - + int8 - + billingevent_a57d1815 - - + + public.BillingEvent - - + + [table] - + billing_event_id - + - + int8 not null - + registrar_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 - + - + 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 - + + recurrence_history_revision_id + + + + + int8 + + billingevent_a57d1815:w->allocationtoken_a08ccbef:e - - - - - - - - + + + + + + + + fk_billing_event_allocation_token billingrecurrence_5fa2cb01 - - + + public.BillingRecurrence - - + + [table] - + billing_recurrence_id - + - + int8 not null - + registrar_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 - + billingevent_a57d1815:w->billingrecurrence_5fa2cb01:e - - - - - - - - + + + + + + + + fk_billing_event_cancellation_matching_billing_recurrence_id domainhistory_a54cc226 - - + + public.DomainHistory - - + + [table] - + history_revision_id - + - + int8 not null - + history_by_superuser - + - + bool not null - + history_registrar_id - + - + text - + history_modification_time - + - + timestamptz not null - + history_reason - + - + text - + history_requested_by_registrar - + - + bool - + history_client_transaction_id - + - + text - + history_server_transaction_id - + - + text - + history_type - + - + text not null - + history_xml_bytes - + - + bytea - + admin_contact - + - + text - + auth_info_repo_id - + - + text - + auth_info_value - + - + text - + billing_recurrence_id - + - + int8 - + autorenew_poll_message_id - + - + int8 - + billing_contact - + - + text - + deletion_poll_message_id - + - + int8 - + domain_name - + - + text - + idn_table_name - + - + text - + last_transfer_time - + - + timestamptz - + launch_notice_accepted_time - + - + timestamptz - + launch_notice_expiration_time - + - + timestamptz - + launch_notice_tcn_id - + - + text - + launch_notice_validator_id - + - + text - + registrant_contact - + - + text - + registration_expiration_time - + - + timestamptz - + smd_id - + - + text - + subordinate_hosts - + - + _text - + tech_contact - + - + text - + tld - + - + text - + transfer_billing_cancellation_id - + - + int8 - + transfer_billing_recurrence_id - + - + int8 - + transfer_autorenew_poll_message_id - + - + int8 - + transfer_billing_event_id - + - + int8 - + transfer_renew_period_unit - + - + text - + transfer_renew_period_value - + - + int4 - + transfer_registration_expiration_time - + - + timestamptz - + transfer_poll_message_id_1 - + - + int8 - + transfer_poll_message_id_2 - + - + int8 - + transfer_client_txn_id - + - + text - + transfer_server_txn_id - + - + text - + transfer_gaining_registrar_id - + - + text - + transfer_losing_registrar_id - + - + text - + transfer_pending_expiration_time - + - + timestamptz - + transfer_request_time - + - + timestamptz - + transfer_status - + - + text - + creation_registrar_id - + - + text - + creation_time - + - + timestamptz - + current_sponsor_registrar_id - + - + text - + deletion_time - + - + timestamptz - + last_epp_update_registrar_id - + - + text - + last_epp_update_time - + - + timestamptz - + statuses - + - + _text - + update_timestamp - + - + timestamptz - + domain_repo_id - + - + text not null - + autorenew_end_time - + - + timestamptz - + history_other_registrar_id - + - + text - + history_period_unit - + - + text - + history_period_value - + - + int4 - + billing_recurrence_history_id - + - + int8 - + autorenew_poll_message_history_id - + - + int8 - + deletion_poll_message_history_id - + - + int8 - + transfer_billing_recurrence_history_id - + - + int8 - + transfer_autorenew_poll_message_history_id - + - + int8 - + transfer_billing_event_history_id - + - + int8 - + transfer_history_entry_id - + - + int8 - + transfer_repo_id - + - + text - + transfer_poll_message_id_3 - + - + int8 - + transfer_billing_cancellation_history_id - + - + int8 - + billingevent_a57d1815:w->domainhistory_a54cc226:e - - - - - - - - + + + + + + + + fk_billing_event_domain_history billingevent_a57d1815:w->domainhistory_a54cc226:e - - - - - - - - + + + + + + + + fk_billing_event_domain_history + + + billingevent_a57d1815:w->domainhistory_a54cc226:e + + + + + + + + + fk_billing_event_recurrence_history + + + + billingevent_a57d1815:w->domainhistory_a54cc226:e + + + + + + + + + fk_billing_event_recurrence_history + registrar_6e1503e3 - - + + public.Registrar - - + + [table] - + registrar_id - + - + text not null - + allowed_tlds - + - + _text - + billing_account_map - + - + "hstore" - + billing_identifier - + - + int8 - + block_premium_names - + - + bool not null - + client_certificate - + - + text - + client_certificate_hash - + - + text - + contacts_require_syncing - + - + bool not null - + creation_time - + - + timestamptz - + drive_folder_id - + - + text - + email_address - + - + text - + failover_client_certificate - + - + text - + failover_client_certificate_hash - + - + text - + fax_number - + - + text - + iana_identifier - + - + int8 - + icann_referral_email - + - + text - + i18n_address_city - + - + text - + i18n_address_country_code - + - + text - + i18n_address_state - + - + text - + i18n_address_street_line1 - + - + text - + i18n_address_street_line2 - + - + text - + i18n_address_street_line3 - + - + text - + i18n_address_zip - + - + text - + ip_address_allow_list - + - + _text - + last_certificate_update_time - + - + timestamptz - + last_update_time - + - + timestamptz not null - + localized_address_city - + - + text - + localized_address_country_code - + - + text - + localized_address_state - + - + text - + localized_address_street_line1 - + - + text - + localized_address_street_line2 - + - + text - + localized_address_street_line3 - + - + text - + localized_address_zip - + - + text - + password_hash - + - + text - + phone_number - + - + text - + phone_passcode - + - + text - + po_number - + - + text - + rdap_base_urls - + - + _text - + registrar_name - + - + text not null - + registry_lock_allowed - + - + bool not null - + password_salt - + - + text - + state - + - + text - + type - + - + text not null - + url - + - + text - + whois_server - + - + text - + - + billingevent_a57d1815:w->registrar_6e1503e3:e - - - - - - - - + + + + + + + + fk_billing_event_registrar_id billingcancellation_6eedf614 - - + + public.BillingCancellation - - + + [table] - + billing_cancellation_id - + - + int8 not null - + registrar_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 - + billing_event_history_id - + - + int8 - + billing_event_domain_repo_id - + - + text - + billing_recurrence_history_id - + - + int8 - + billing_recurrence_domain_repo_id - + - + text - + billingcancellation_6eedf614:w->billingevent_a57d1815:e - - - - - - - - + + + + + + + + fk_billing_cancellation_billing_event_id billingcancellation_6eedf614:w->billingrecurrence_5fa2cb01:e - - - - - - - - + + + + + + + + fk_billing_cancellation_billing_recurrence_id billingcancellation_6eedf614:w->domainhistory_a54cc226:e - - - - - - - - + + + + + + + + fk_billing_cancellation_domain_history billingcancellation_6eedf614:w->domainhistory_a54cc226:e - - - - - - - - + + + + + + + + fk_billing_cancellation_domain_history - + billingcancellation_6eedf614:w->registrar_6e1503e3:e - - - - - - - - + + + + + + + + fk_billing_cancellation_registrar_id domain_6c51cffa - - + + public.Domain - - + + [table] - + repo_id - + - + text not null - + creation_registrar_id - + - + text not null - + creation_time - + - + timestamptz not null - + current_sponsor_registrar_id - + - + text not null - + deletion_time - + - + timestamptz - + last_epp_update_registrar_id - + - + text - + last_epp_update_time - + - + timestamptz - + statuses - + - + _text - + auth_info_repo_id - + - + text - + auth_info_value - + - + text - + domain_name - + - + text - + idn_table_name - + - + text - + last_transfer_time - + - + timestamptz - + launch_notice_accepted_time - + - + timestamptz - + launch_notice_expiration_time - + - + timestamptz - + launch_notice_tcn_id - + - + text - + launch_notice_validator_id - + - + text - + registration_expiration_time - + - + timestamptz - + smd_id - + - + text - + subordinate_hosts - + - + _text - + tld - + - + text - + admin_contact - + - + text - + billing_contact - + - + text - + registrant_contact - + - + text - + tech_contact - + - + text - + transfer_poll_message_id_1 - + - + int8 - + transfer_poll_message_id_2 - + - + int8 - + transfer_billing_cancellation_id - + - + int8 - + transfer_billing_event_id - + - + int8 - + transfer_billing_recurrence_id - + - + int8 - + transfer_autorenew_poll_message_id - + - + int8 - + transfer_renew_period_unit - + - + text - + transfer_renew_period_value - + - + int4 - + transfer_client_txn_id - + - + text - + transfer_server_txn_id - + - + text - + transfer_registration_expiration_time - + - + timestamptz - + transfer_gaining_registrar_id - + - + text - + transfer_losing_registrar_id - + - + text - + transfer_pending_expiration_time - + - + timestamptz - + transfer_request_time - + - + timestamptz - + transfer_status - + - + text - + update_timestamp - + - + timestamptz - + billing_recurrence_id - + - + int8 - + autorenew_poll_message_id - + - + int8 - + deletion_poll_message_id - + - + int8 - + autorenew_end_time - + - + timestamptz - + billing_recurrence_history_id - + - + int8 - + autorenew_poll_message_history_id - + - + int8 - + deletion_poll_message_history_id - + - + int8 - + transfer_billing_recurrence_history_id - + - + int8 - + transfer_autorenew_poll_message_history_id - + - + int8 - + transfer_billing_event_history_id - + - + int8 - + transfer_history_entry_id - + - + int8 - + transfer_repo_id - + - + text - + transfer_poll_message_id_3 - + - + int8 - + transfer_billing_cancellation_history_id - + - + int8 - + domain_6c51cffa:w->billingevent_a57d1815:e - - - - - - - - + + + + + + + + fk_domain_transfer_billing_event_id domain_6c51cffa:w->billingcancellation_6eedf614:e - - - - - - - - + + + + + + + + fk_domain_transfer_billing_cancellation_id domain_6c51cffa:w->billingrecurrence_5fa2cb01:e - - - - - - - - + + + + + + + + fk_domain_billing_recurrence_id domain_6c51cffa:w->billingrecurrence_5fa2cb01:e - - - - - - - - + + + + + + + + fk_domain_transfer_billing_recurrence_id contact_8de8cb16 - - + + public.Contact - - + + [table] - + repo_id - + - + text not null - + creation_registrar_id - + - + text not null - + creation_time - + - + timestamptz not null - + current_sponsor_registrar_id - + - + text not null - + deletion_time - + - + timestamptz - + last_epp_update_registrar_id - + - + text - + last_epp_update_time - + - + timestamptz - + statuses - + - + _text - + auth_info_repo_id - + - + text - + auth_info_value - + - + text - + contact_id - + - + text - + disclose_types_addr - + - + _text - + disclose_show_email - + - + bool - + disclose_show_fax - + - + bool - + disclose_mode_flag - + - + bool - + disclose_types_name - + - + _text - + disclose_types_org - + - + _text - + disclose_show_voice - + - + bool - + email - + - + text - + fax_phone_extension - + - + text - + fax_phone_number - + - + text - + addr_i18n_city - + - + text - + addr_i18n_country_code - + - + text - + addr_i18n_state - + - + text - + addr_i18n_street_line1 - + - + text - + addr_i18n_street_line2 - + - + text - + addr_i18n_street_line3 - + - + text - + addr_i18n_zip - + - + text - + addr_i18n_name - + - + text - + addr_i18n_org - + - + text - + addr_i18n_type - + - + text - + last_transfer_time - + - + timestamptz - + addr_local_city - + - + text - + addr_local_country_code - + - + text - + addr_local_state - + - + text - + addr_local_street_line1 - + - + text - + addr_local_street_line2 - + - + text - + addr_local_street_line3 - + - + text - + addr_local_zip - + - + text - + addr_local_name - + - + text - + addr_local_org - + - + text - + addr_local_type - + - + text - + search_name - + - + text - + voice_phone_extension - + - + text - + voice_phone_number - + - + text - + transfer_poll_message_id_1 - + - + int8 - + transfer_poll_message_id_2 - + - + int8 - + transfer_client_txn_id - + - + text - + transfer_server_txn_id - + - + text - + transfer_gaining_registrar_id - + - + text - + transfer_losing_registrar_id - + - + text - + transfer_pending_expiration_time - + - + timestamptz - + transfer_request_time - + - + timestamptz - + transfer_status - + - + text - + update_timestamp - + - + timestamptz - + transfer_history_entry_id - + - + int8 - + transfer_repo_id - + - + text - + transfer_poll_message_id_3 - + - + int8 - + domain_6c51cffa:w->contact_8de8cb16:e - - - - - - - - + + + + + + + + fk_domain_admin_contact domain_6c51cffa:w->contact_8de8cb16:e - - - - - - - - + + + + + + + + fk_domain_billing_contact domain_6c51cffa:w->contact_8de8cb16:e - - - - - - - - + + + + + + + + fk_domain_registrant_contact domain_6c51cffa:w->contact_8de8cb16:e - - - - - - - - + + + + + + + + fk_domain_tech_contact - - domain_6c51cffa:w->registrar_6e1503e3:e - - - - - - - - - fk2jc69qyg2tv9hhnmif6oa1cx1 - - - - domain_6c51cffa:w->registrar_6e1503e3:e - - - - - - - - - fk2u3srsfbei272093m3b3xwj23 - - domain_6c51cffa:w->registrar_6e1503e3:e - - - - - - - - - fkjc0r9r5y1lfbt4gpbqw4wsuvq + + + + + + + + + fk2jc69qyg2tv9hhnmif6oa1cx1 domain_6c51cffa:w->registrar_6e1503e3:e - - - - - - - - - fk_domain_transfer_gaining_registrar_id + + + + + + + + + fk2u3srsfbei272093m3b3xwj23 domain_6c51cffa:w->registrar_6e1503e3:e - - - - - - - - + + + + + + + + + fkjc0r9r5y1lfbt4gpbqw4wsuvq + + + + domain_6c51cffa:w->registrar_6e1503e3:e + + + + + + + + + fk_domain_transfer_gaining_registrar_id + + + + domain_6c51cffa:w->registrar_6e1503e3:e + + + + + + + + fk_domain_transfer_losing_registrar_id tld_f1fa57e2 - - + + public.Tld - - + + [table] - + tld_name - + - + text not null - + add_grace_period_length - + - + interval not null - + allowed_fully_qualified_host_names - + - + _text - + allowed_registrant_contact_ids - + - + _text - + anchor_tenant_add_grace_period_length - + - + interval not null - + auto_renew_grace_period_length - + - + interval not null - + automatic_transfer_length - + - + interval not null - + claims_period_end - + - + timestamptz not null - + create_billing_cost_amount - + - + numeric(19, 2) - + create_billing_cost_currency - + - + text - + creation_time - + - + timestamptz not null - + currency - + - + text not null - + dns_paused - + - + bool not null - + dns_writers - + - + _text not null - + drive_folder_id - + - + text - + eap_fee_schedule - + - + "hstore" not null - + escrow_enabled - + - + bool not null - + invoicing_enabled - + - + bool not null - + lordn_username - + - + text - + num_dns_publish_locks - + - + int4 not null - + pending_delete_length - + - + interval not null - + premium_list_name - + - + text - + pricing_engine_class_name - + - + text - + redemption_grace_period_length - + - + interval not null - + registry_lock_or_unlock_cost_amount - + - + numeric(19, 2) - + registry_lock_or_unlock_cost_currency - + - + text - + renew_billing_cost_transitions - + - + "hstore" not null - + renew_grace_period_length - + - + interval not null - + reserved_list_names - + - + _text - + restore_billing_cost_amount - + - + numeric(19, 2) - + restore_billing_cost_currency - + - + text - + roid_suffix - + - + text - + server_status_change_billing_cost_amount - + - + numeric(19, 2) - + server_status_change_billing_cost_currency - + - + text - + tld_state_transitions - + - + "hstore" not null - + tld_type - + - + text not null - + tld_unicode - + - + text not null - + transfer_grace_period_length - + - + interval not null - + - + domain_6c51cffa:w->tld_f1fa57e2:e - - - - - - - - + + + + + + + + fk_domain_tld graceperiod_cd3b2e8f - - + + public.GracePeriod - - + + [table] - + grace_period_id - + - + int8 not null - + billing_event_id - + - + int8 - + billing_recurrence_id - + - + int8 - + registrar_id - + - + text not null - + domain_repo_id - + - + text not null - + expiration_time - + - + timestamptz not null - + type - + - + text not null - + billing_event_history_id - + - + int8 - + billing_recurrence_history_id - + - + int8 - + billing_event_domain_repo_id - + - + text - + billing_recurrence_domain_repo_id - + - + text - + graceperiod_cd3b2e8f:w->billingevent_a57d1815:e - - - - - - - - + + + + + + + + fk_grace_period_billing_event_id graceperiod_cd3b2e8f:w->domain_6c51cffa:e - - - - - - - - + + + + + + + + fk_grace_period_domain_repo_id graceperiod_cd3b2e8f:w->billingrecurrence_5fa2cb01:e - - - - - - - - + + + + + + + + fk_grace_period_billing_recurrence_id - + graceperiod_cd3b2e8f:w->registrar_6e1503e3:e - - - - - - - - + + + + + + + + fk_grace_period_registrar_id - + billingrecurrence_5fa2cb01:w->domainhistory_a54cc226:e - - - - - - - - + + + + + + + + fk_billing_recurrence_domain_history - + billingrecurrence_5fa2cb01:w->domainhistory_a54cc226:e - - - - - - - - + + + + + + + + fk_billing_recurrence_domain_history - + billingrecurrence_5fa2cb01:w->registrar_6e1503e3:e - - - - - - - - + + + + + + + + fk_billing_recurrence_registrar_id claimsentry_105da9f1 - - + + public.ClaimsEntry - - + + [table] - + revision_id - + - + int8 not null - + claim_key - + - + text not null - + domain_label - + - + text not null - + claimslist_3d49bc2b - - + + public.ClaimsList - - + + [table] - + revision_id - + - + bigserial not null - + - + auto-incremented - + creation_timestamp - + - + timestamptz not null - + tmdb_generation_time - + - + timestamptz not null - + claimsentry_105da9f1:w->claimslist_3d49bc2b:e - - - - - - - - + + + + + + + + fk6sc6at5hedffc0nhdcab6ivuq - - contact_8de8cb16:w->registrar_6e1503e3:e - - - - - - - - - fk1sfyj7o7954prbn1exk7lpnoe - - - - contact_8de8cb16:w->registrar_6e1503e3:e - - - - - - - - - fk93c185fx7chn68uv7nl6uv2s0 - - contact_8de8cb16:w->registrar_6e1503e3:e - - - - - - - - - fkmb7tdiv85863134w1wogtxrb2 + + + + + + + + + fk1sfyj7o7954prbn1exk7lpnoe contact_8de8cb16:w->registrar_6e1503e3:e - - - - - - - - - fk_contact_transfer_gaining_registrar_id + + + + + + + + + fk93c185fx7chn68uv7nl6uv2s0 contact_8de8cb16:w->registrar_6e1503e3:e - - - - - - - - + + + + + + + + + fkmb7tdiv85863134w1wogtxrb2 + + + + contact_8de8cb16:w->registrar_6e1503e3:e + + + + + + + + + fk_contact_transfer_gaining_registrar_id + + + + contact_8de8cb16:w->registrar_6e1503e3:e + + + + + + + + fk_contact_transfer_losing_registrar_id contacthistory_d2964f8a - - + + public.ContactHistory - - + + [table] - + history_revision_id - + - + int8 not null - + history_by_superuser - + - + bool not null - + history_registrar_id - + - + text - + history_modification_time - + - + timestamptz not null - + history_reason - + - + text - + history_requested_by_registrar - + - + bool - + history_client_transaction_id - + - + text - + history_server_transaction_id - + - + text - + history_type - + - + text not null - + history_xml_bytes - + - + bytea - + auth_info_repo_id - + - + text - + auth_info_value - + - + text - + contact_id - + - + text - + disclose_types_addr - + - + _text - + disclose_show_email - + - + bool - + disclose_show_fax - + - + bool - + disclose_mode_flag - + - + bool - + disclose_types_name - + - + _text - + disclose_types_org - + - + _text - + disclose_show_voice - + - + bool - + email - + - + text - + fax_phone_extension - + - + text - + fax_phone_number - + - + text - + addr_i18n_city - + - + text - + addr_i18n_country_code - + - + text - + addr_i18n_state - + - + text - + addr_i18n_street_line1 - + - + text - + addr_i18n_street_line2 - + - + text - + addr_i18n_street_line3 - + - + text - + addr_i18n_zip - + - + text - + addr_i18n_name - + - + text - + addr_i18n_org - + - + text - + addr_i18n_type - + - + text - + last_transfer_time - + - + timestamptz - + addr_local_city - + - + text - + addr_local_country_code - + - + text - + addr_local_state - + - + text - + addr_local_street_line1 - + - + text - + addr_local_street_line2 - + - + text - + addr_local_street_line3 - + - + text - + addr_local_zip - + - + text - + addr_local_name - + - + text - + addr_local_org - + - + text - + addr_local_type - + - + text - + search_name - + - + text - + transfer_poll_message_id_1 - + - + int8 - + transfer_poll_message_id_2 - + - + int8 - + transfer_client_txn_id - + - + text - + transfer_server_txn_id - + - + text - + transfer_gaining_registrar_id - + - + text - + transfer_losing_registrar_id - + - + text - + transfer_pending_expiration_time - + - + timestamptz - + transfer_request_time - + - + timestamptz - + transfer_status - + - + text - + voice_phone_extension - + - + text - + voice_phone_number - + - + text - + creation_registrar_id - + - + text - + creation_time - + - + timestamptz - + current_sponsor_registrar_id - + - + text - + deletion_time - + - + timestamptz - + last_epp_update_registrar_id - + - + text - + last_epp_update_time - + - + timestamptz - + statuses - + - + _text - + contact_repo_id - + - + text not null - + update_timestamp - + - + timestamptz - + transfer_history_entry_id - + - + int8 - + transfer_repo_id - + - + text - + transfer_poll_message_id_3 - + - + int8 - + contacthistory_d2964f8a:w->contact_8de8cb16:e - - - - - - - - + + + + + + + + fk_contact_history_contact_repo_id - + contacthistory_d2964f8a:w->registrar_6e1503e3:e - - - - - - - - + + + + + + + + fk_contact_history_registrar_id pollmessage_614a523e - - + + public.PollMessage - - + + [table] - + type - + - + text not null - + poll_message_id - + - + int8 not null - + registrar_id - + - + text not null - + contact_repo_id - + - + text - + contact_history_revision_id - + - + int8 - + domain_repo_id - + - + text - + domain_history_revision_id - + - + int8 - + event_time - + - + timestamptz not null - + host_repo_id - + - + text - + host_history_revision_id - + - + int8 - + message - + - + text - + transfer_response_contact_id - + - + text - + transfer_response_domain_expiration_time - + - + timestamptz - + transfer_response_domain_name - + - + text - + pending_action_response_action_result - + - + bool - + pending_action_response_name_or_id - + - + text - + pending_action_response_processed_date - + - + timestamptz - + pending_action_response_client_txn_id - + - + text - + pending_action_response_server_txn_id - + - + text - + transfer_response_gaining_registrar_id - + - + text - + transfer_response_losing_registrar_id - + - + text - + transfer_response_pending_transfer_expiration_time - + - + timestamptz - + transfer_response_transfer_request_time - + - + timestamptz - + transfer_response_transfer_status - + - + text - + autorenew_end_time - + - + timestamptz - + autorenew_domain_name - + - + text - + pollmessage_614a523e:w->domain_6c51cffa:e - - - - - - - - + + + + + + + + fk_poll_message_domain_repo_id pollmessage_614a523e:w->contact_8de8cb16:e - - - - - - - - + + + + + + + + fk_poll_message_contact_repo_id pollmessage_614a523e:w->contacthistory_d2964f8a:e - - - - - - - - + + + + + + + + fk_poll_message_contact_history pollmessage_614a523e:w->contacthistory_d2964f8a:e - - - - - - - - + + + + + + + + fk_poll_message_contact_history - + pollmessage_614a523e:w->domainhistory_a54cc226:e - - - - - - - - + + + + + + + + fk_poll_message_domain_history - + pollmessage_614a523e:w->domainhistory_a54cc226:e - - - - - - - - + + + + + + + + fk_poll_message_domain_history host_f21b78de - - + + public.Host - - + + [table] - + repo_id - + - + text not null - + creation_registrar_id - + - + text - + creation_time - + - + timestamptz - + current_sponsor_registrar_id - + - + text - + deletion_time - + - + timestamptz - + last_epp_update_registrar_id - + - + text - + last_epp_update_time - + - + timestamptz - + statuses - + - + _text - + host_name - + - + text - + last_superordinate_change - + - + timestamptz - + last_transfer_time - + - + timestamptz - + superordinate_domain - + - + text - + inet_addresses - + - + _text - + update_timestamp - + - + timestamptz - + transfer_poll_message_id_3 - + - + int8 - + - + pollmessage_614a523e:w->host_f21b78de:e - - - - - - - - + + + + + + + + fk_poll_message_host_repo_id hosthistory_56210c2 - - + + public.HostHistory - - + + [table] - + history_revision_id - + - + int8 not null - + history_by_superuser - + - + bool not null - + history_registrar_id - + - + text not null - + history_modification_time - + - + timestamptz not null - + history_reason - + - + text - + history_requested_by_registrar - + - + bool - + history_client_transaction_id - + - + text - + history_server_transaction_id - + - + text - + history_type - + - + text not null - + history_xml_bytes - + - + bytea - + host_name - + - + text - + inet_addresses - + - + _text - + last_superordinate_change - + - + timestamptz - + last_transfer_time - + - + timestamptz - + superordinate_domain - + - + text - + creation_registrar_id - + - + text - + creation_time - + - + timestamptz - + current_sponsor_registrar_id - + - + text - + deletion_time - + - + timestamptz - + last_epp_update_registrar_id - + - + text - + last_epp_update_time - + - + timestamptz - + statuses - + - + _text - + host_repo_id - + - + text not null - + update_timestamp - + - + timestamptz - + transfer_poll_message_id_3 - + - + int8 - + - + pollmessage_614a523e:w->hosthistory_56210c2:e - - - - - - - - + + + + + + + + fk_poll_message_host_history - + pollmessage_614a523e:w->hosthistory_56210c2:e - - - - - - - - + + + + + + + + fk_poll_message_host_history - - pollmessage_614a523e:w->registrar_6e1503e3:e - - - - - - - - - fk_poll_message_registrar_id - - - - pollmessage_614a523e:w->registrar_6e1503e3:e - - - - - - - - - fk_poll_message_transfer_response_gaining_registrar_id - - pollmessage_614a523e:w->registrar_6e1503e3:e - - - - - - - - + + + + + + + + + fk_poll_message_registrar_id + + + + pollmessage_614a523e:w->registrar_6e1503e3:e + + + + + + + + + fk_poll_message_transfer_response_gaining_registrar_id + + + + pollmessage_614a523e:w->registrar_6e1503e3:e + + + + + + + + fk_poll_message_transfer_response_losing_registrar_id cursor_6af40e8c - - + + public."Cursor" - - + + [table] - + "scope" - + - + text not null - + type - + - + text not null - + cursor_time - + - + timestamptz not null - + last_update_time - + - + timestamptz not null - + delegationsignerdata_e542a872 - - + + public.DelegationSignerData - - + + [table] - + domain_repo_id - + - + text not null - + key_tag - + - + int4 not null - + algorithm - + - + int4 not null - + digest - + - + bytea not null - + digest_type - + - + int4 not null - + delegationsignerdata_e542a872:w->domain_6c51cffa:e - - - - - - - - + + + + + + + + fktr24j9v14ph2mfuw2gsmt12kq domainhistory_a54cc226:w->domain_6c51cffa:e - - - - - - - - + + + + + + + + fk_domain_history_domain_repo_id - + domainhistory_a54cc226:w->registrar_6e1503e3:e - - - - - - - - + + + + + + + + fk_domain_history_registrar_id domainhost_1ea127c2 - - + + public.DomainHost - - + + [table] - + domain_repo_id - + - + text not null - + host_repo_id - + - + text - + domainhost_1ea127c2:w->domain_6c51cffa:e - - - - - - - - + + + + + + + + fkfmi7bdink53swivs390m2btxg - + domainhost_1ea127c2:w->host_f21b78de:e - - - - - - - - + + + + + + + + fk_domainhost_host_valid host_f21b78de:w->domain_6c51cffa:e - - - - - - - - + + + + + + + + fk_host_superordinate_domain - - host_f21b78de:w->registrar_6e1503e3:e - - - - - - - - - fk_host_creation_registrar_id - - - - host_f21b78de:w->registrar_6e1503e3:e - - - - - - - - - fk_host_current_sponsor_registrar_id - - host_f21b78de:w->registrar_6e1503e3:e - - - - - - - - + + + + + + + + + fk_host_creation_registrar_id + + + + host_f21b78de:w->registrar_6e1503e3:e + + + + + + + + + fk_host_current_sponsor_registrar_id + + + + host_f21b78de:w->registrar_6e1503e3:e + + + + + + + + fk_host_last_epp_update_registrar_id domaindsdatahistory_995b060d - - + + public.DomainDsDataHistory - - + + [table] - + ds_data_history_revision_id - + - + int8 not null - + algorithm - + - + int4 not null - + digest - + - + bytea not null - + digest_type - + - + int4 not null - + domain_history_revision_id - + - + int8 not null - + key_tag - + - + int4 not null - + domain_repo_id - + - + text - + - + domaindsdatahistory_995b060d:w->domainhistory_a54cc226:e - - - - - - - - + + + + + + + + fko4ilgyyfnvppbpuivus565i0j - + domaindsdatahistory_995b060d:w->domainhistory_a54cc226:e - - - - - - - - + + + + + + + + fko4ilgyyfnvppbpuivus565i0j domainhistoryhost_9f3f23ee - - + + public.DomainHistoryHost - - + + [table] - + domain_history_history_revision_id - + - + int8 not null - + host_repo_id - + - + text - + domain_history_domain_repo_id - + - + text not null - + - + domainhistoryhost_9f3f23ee:w->domainhistory_a54cc226:e - - - - - - - - + + + + + + + + fka9woh3hu8gx5x0vly6bai327n - + domainhistoryhost_9f3f23ee:w->domainhistory_a54cc226:e - - - - - - - - + + + + + + + + fka9woh3hu8gx5x0vly6bai327n domaintransactionrecord_6e77ff61 - - + + public.DomainTransactionRecord - - + + [table] - + id - + - + bigserial not null - + - + auto-incremented - + report_amount - + - + int4 not null - + report_field - + - + text not null - + reporting_time - + - + timestamptz not null - + tld - + - + text not null - + domain_repo_id - + - + text - + history_revision_id - + - + int8 - + - + domaintransactionrecord_6e77ff61:w->domainhistory_a54cc226:e - - - - - - - - + + + + + + + + fkcjqe54u72kha71vkibvxhjye7 - + domaintransactionrecord_6e77ff61:w->domainhistory_a54cc226:e - - - - - - - - + + + + + + + + fkcjqe54u72kha71vkibvxhjye7 - + domaintransactionrecord_6e77ff61:w->tld_f1fa57e2:e - - - - - - - - + + + + + + + + fk_domain_transaction_record_tld graceperiodhistory_40ccc1f1 - - + + public.GracePeriodHistory - - + + [table] - + grace_period_history_revision_id - + - + int8 not null - + billing_event_id - + - + int8 - + billing_event_history_id - + - + int8 - + billing_recurrence_id - + - + int8 - + billing_recurrence_history_id - + - + int8 - + registrar_id - + - + text not null - + domain_repo_id - + - + text not null - + expiration_time - + - + timestamptz not null - + type - + - + text not null - + domain_history_revision_id - + - + int8 - + grace_period_id - + - + int8 not null - + billing_event_domain_repo_id - + - + text - + billing_recurrence_domain_repo_id - + - + text - + - + graceperiodhistory_40ccc1f1:w->domainhistory_a54cc226:e - - - - - - - - + + + + + + + + fk7w3cx8d55q8bln80e716tr7b8 - + graceperiodhistory_40ccc1f1:w->domainhistory_a54cc226:e - - - - - - - - + + + + + + + + fk7w3cx8d55q8bln80e716tr7b8 - + hosthistory_56210c2:w->host_f21b78de:e - - - - - - - - + + + + + + + + fk_hosthistory_host - + hosthistory_56210c2:w->registrar_6e1503e3:e - - - - - - - - + + + + + + + + fk_history_registrar_id kmssecret_f3b28857 - - + + public.KmsSecret - - + + [table] - + revision_id - + - + int8 not null - + creation_time - + - + timestamptz not null - + encrypted_value - + - + text not null - + crypto_key_version_name - + - + text not null - + secret_name - + - + text not null - + lock_f21d4861 - - + + public.Lock - - + + [table] - + resource_name - + - + text not null - + "scope" - + - + text not null - + acquired_time - + - + timestamptz not null - + expiration_time - + - + timestamptz not null - + request_log_id - + - + text not null - + premiumentry_b0060b91 - - + + public.PremiumEntry - - + + [table] - + revision_id - + - + int8 not null - + price - + - + numeric(19, 2) not null - + domain_label - + - + text not null - + premiumlist_7c3ea68b - - + + public.PremiumList - - + + [table] - + revision_id - + - + bigserial not null - + - + auto-incremented - + creation_timestamp - + - + timestamptz - + name - + - + text not null - + bloom_filter - + - + bytea not null - + currency - + - + text not null - + - + premiumentry_b0060b91:w->premiumlist_7c3ea68b:e - - - - - - - - + + + + + + + + fko0gw90lpo1tuee56l0nb6y6g5 rderevision_83396864 - - + + public.RdeRevision - - + + [table] - + tld - + - + text not null - + mode - + - + text not null - + "date" - + - + date not null - + update_timestamp - + - + timestamptz - + revision - + - + int4 not null - + registrarpoc_ab47054d - - + + public.RegistrarPoc - - + + [table] - + email_address - + - + text not null - + allowed_to_set_registry_lock_password - + - + bool not null - + fax_number - + - + text - + gae_user_id - + - + text - + name - + - + text - + phone_number - + - + text - + registry_lock_password_hash - + - + text - + registry_lock_password_salt - + - + text - + types - + - + _text - + visible_in_domain_whois_as_abuse - + - + bool not null - + visible_in_whois_as_admin - + - + bool not null - + visible_in_whois_as_tech - + - + bool not null - + registry_lock_email_address - + - + text - + registrar_id - + - + text not null - + - + registrarpoc_ab47054d:w->registrar_6e1503e3:e - - - - - - - - + + + + + + + + fk_registrar_poc_registrar_id registrylock_ac88663e - - + + public.RegistryLock - - + + [table] - + revision_id - + - + bigserial not null - + - + auto-incremented - + lock_completion_time - + - + timestamptz - + lock_request_time - + - + timestamptz not null - + domain_name - + - + text not null - + is_superuser - + - + bool not null - + registrar_id - + - + text not null - + registrar_poc_id - + - + text - + repo_id - + - + text not null - + verification_code - + - + text not null - + unlock_request_time - + - + timestamptz - + unlock_completion_time - + - + timestamptz - + last_update_time - + - + timestamptz not null - + relock_revision_id - + - + int8 - + relock_duration - + - + interval - + - + registrylock_ac88663e:w->registrylock_ac88663e:e - - - - - - - - + + + + + + + + fk2lhcwpxlnqijr96irylrh1707 reservedentry_1a7b8520 - - + + public.ReservedEntry - - + + [table] - + revision_id - + - + int8 not null - + comment - + - + text - + reservation_type - + - + int4 not null - + domain_label - + - + text not null - + reservedlist_b97c3f1c - - + + public.ReservedList - - + + [table] - + revision_id - + - + bigserial not null - + - + auto-incremented - + creation_timestamp - + - + timestamptz not null - + name - + - + text not null - + should_publish - + - + bool not null - + - + reservedentry_1a7b8520:w->reservedlist_b97c3f1c:e - - - - - - - - + + + + + + + + fkgq03rk0bt1hb915dnyvd3vnfc serversecret_6cc90f09 - - + + public.ServerSecret - - + + [table] - + secret - + - + uuid not null - + id - + - + int8 not null - + signedmarkrevocationentry_99c39721 - - + + public.SignedMarkRevocationEntry - - + + [table] - + revision_id - + - + int8 not null - + revocation_time - + - + timestamptz not null - + smd_id - + - + text not null - + signedmarkrevocationlist_c5d968fb - - + + public.SignedMarkRevocationList - - + + [table] - + revision_id - + - + bigserial not null - + - + auto-incremented - + creation_time - + - + timestamptz - + - + signedmarkrevocationentry_99c39721:w->signedmarkrevocationlist_c5d968fb:e - - - - - - - - + + + + + + + + fk5ivlhvs3121yx2li5tqh54u4 spec11threatmatch_a61228a6 - - + + public.Spec11ThreatMatch - - + + [table] - + id - + - + bigserial not null - + - + auto-incremented - + check_date - + - + date not null - + domain_name - + - + text not null - + domain_repo_id - + - + text not null - + registrar_id - + - + text not null - + threat_types - + - + _text not null - + tld - + - + text not null - + sqlreplaycheckpoint_342081b3 - - + + public.SqlReplayCheckpoint - - + + [table] - + id - + - + int8 not null - + last_replay_time - + - + timestamptz not null - + tmchcrl_d282355 - - + + public.TmchCrl - - + + [table] - + certificate_revocations - + - + text not null - + update_timestamp - + - + timestamptz not null - + url - + - + text not null - + id - + - + int8 not null - + transaction_d50389d4 - - + + public.Transaction - - + + [table] - + id - + - + bigserial not null - + - + auto-incremented - + contents - + - + bytea - + @@ -6964,6 +6998,11 @@ td.section { synthetic_creation_time timestamptz +
+ + recurrence_history_revision_id + int8 +
@@ -7080,6 +7119,23 @@ td.section {
+
+ fk_billing_event_recurrence_history + [foreign key, with no action] +
+
+ + domain_repo_id (0..many)→ public.DomainHistory.domain_repo_id + +
+
+ + recurrence_history_revision_id (0..many)→ public.DomainHistory.history_revision_id + +
+
+ +
Indexes
@@ -10020,6 +10076,23 @@ td.section {
+
+ fk_billing_event_recurrence_history + [foreign key, with no action] +
+
+ + domain_repo_id ←(0..many) public.BillingEvent.domain_repo_id + +
+
+ + history_revision_id ←(0..many) public.BillingEvent.recurrence_history_revision_id + +
+
+ +
fk_billing_recurrence_domain_history [foreign key, with no action] diff --git a/db/src/main/resources/sql/flyway.txt b/db/src/main/resources/sql/flyway.txt index 9a3fc8c62..27ed01983 100644 --- a/db/src/main/resources/sql/flyway.txt +++ b/db/src/main/resources/sql/flyway.txt @@ -94,3 +94,4 @@ V93__defer_all_fkeys.sql V94__rename_lock_scope.sql V95__add_contacts_indexes_on_domain.sql V96__rename_sql_checkpoint_fields.sql +V97__add_recurrence_history_id_column_to_onetime.sql diff --git a/db/src/main/resources/sql/flyway/V97__add_recurrence_history_id_column_to_onetime.sql b/db/src/main/resources/sql/flyway/V97__add_recurrence_history_id_column_to_onetime.sql new file mode 100644 index 000000000..2ac685ded --- /dev/null +++ b/db/src/main/resources/sql/flyway/V97__add_recurrence_history_id_column_to_onetime.sql @@ -0,0 +1,21 @@ +-- Copyright 2021 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. + +ALTER TABLE "BillingEvent" + ADD COLUMN IF NOT EXISTS "recurrence_history_revision_id" INT8; + +ALTER TABLE "BillingEvent" ADD CONSTRAINT fk_billing_event_recurrence_history + FOREIGN KEY (domain_repo_id, recurrence_history_revision_id) + REFERENCES "DomainHistory"(domain_repo_id, history_revision_id) + DEFERRABLE INITIALLY DEFERRED; 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 c2d87aaa6..0960ec13b 100644 --- a/db/src/main/resources/sql/schema/db-schema.sql.generated +++ b/db/src/main/resources/sql/schema/db-schema.sql.generated @@ -63,6 +63,7 @@ cost_amount numeric(19, 2), cost_currency text, period_years int4, + recurrence_history_revision_id int8, synthetic_creation_time timestamptz, primary key (billing_event_id) ); diff --git a/db/src/main/resources/sql/schema/nomulus.golden.sql b/db/src/main/resources/sql/schema/nomulus.golden.sql index 923e49863..97014e680 100644 --- a/db/src/main/resources/sql/schema/nomulus.golden.sql +++ b/db/src/main/resources/sql/schema/nomulus.golden.sql @@ -97,7 +97,8 @@ CREATE TABLE public."BillingEvent" ( cost_amount numeric(19,2), cost_currency text, period_years integer, - synthetic_creation_time timestamp with time zone + synthetic_creation_time timestamp with time zone, + recurrence_history_revision_id bigint ); @@ -1985,6 +1986,14 @@ ALTER TABLE ONLY public."BillingEvent" ADD CONSTRAINT fk_billing_event_domain_history FOREIGN KEY (domain_repo_id, domain_history_revision_id) REFERENCES public."DomainHistory"(domain_repo_id, history_revision_id) DEFERRABLE INITIALLY DEFERRED; +-- +-- Name: BillingEvent fk_billing_event_recurrence_history; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public."BillingEvent" + ADD CONSTRAINT fk_billing_event_recurrence_history FOREIGN KEY (domain_repo_id, recurrence_history_revision_id) REFERENCES public."DomainHistory"(domain_repo_id, history_revision_id) DEFERRABLE INITIALLY DEFERRED; + + -- -- Name: BillingEvent fk_billing_event_registrar_id; Type: FK CONSTRAINT; Schema: public; Owner: - --