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: - --