mirror of
https://github.com/google/nomulus.git
synced 2025-05-22 20:29:36 +02:00
Migrate Registry objects to a TLD table in Cloud SQL (#803)
* Add TLD table * Change reservedLists to array * Change ReservedLists back to a set * Rename reservedListKeyConverter to ReservedListKeySetConverter * Add a postload method
This commit is contained in:
parent
b6ed1982c3
commit
5b78844a94
10 changed files with 335 additions and 105 deletions
|
@ -70,6 +70,13 @@ import java.util.concurrent.ExecutionException;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
import javax.persistence.AttributeOverride;
|
||||||
|
import javax.persistence.AttributeOverrides;
|
||||||
|
import javax.persistence.Column;
|
||||||
|
import javax.persistence.EnumType;
|
||||||
|
import javax.persistence.Enumerated;
|
||||||
|
import javax.persistence.PostLoad;
|
||||||
|
import javax.persistence.Transient;
|
||||||
import org.joda.money.CurrencyUnit;
|
import org.joda.money.CurrencyUnit;
|
||||||
import org.joda.money.Money;
|
import org.joda.money.Money;
|
||||||
import org.joda.time.DateTime;
|
import org.joda.time.DateTime;
|
||||||
|
@ -78,21 +85,31 @@ import org.joda.time.Duration;
|
||||||
/** Persisted per-TLD configuration data. */
|
/** Persisted per-TLD configuration data. */
|
||||||
@ReportedOn
|
@ReportedOn
|
||||||
@Entity
|
@Entity
|
||||||
|
@javax.persistence.Entity(name = "Tld")
|
||||||
public class Registry extends ImmutableObject implements Buildable {
|
public class Registry extends ImmutableObject implements Buildable {
|
||||||
|
|
||||||
@Parent Key<EntityGroupRoot> parent = getCrossTldKey();
|
@Parent @Transient Key<EntityGroupRoot> parent = getCrossTldKey();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The canonical string representation of the TLD associated with this {@link Registry}, which is
|
* The canonical string representation of the TLD associated with this {@link Registry}, which is
|
||||||
* the standard ASCII for regular TLDs and punycoded ASCII for IDN TLDs.
|
* the standard ASCII for regular TLDs and punycoded ASCII for IDN TLDs.
|
||||||
*/
|
*/
|
||||||
@Id String tldStrId;
|
@Id
|
||||||
|
@javax.persistence.Id
|
||||||
|
@Column(name = "tld_name", nullable = false)
|
||||||
|
String tldStrId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A duplicate of {@link #tldStrId}, to simplify BigQuery reporting since the id field becomes
|
* A duplicate of {@link #tldStrId}, to simplify BigQuery reporting since the id field becomes
|
||||||
* {@code __key__.name} rather than being exported as a named field.
|
* {@code __key__.name} rather than being exported as a named field.
|
||||||
*/
|
*/
|
||||||
String tldStr;
|
@Transient String tldStr;
|
||||||
|
|
||||||
|
/** Sets the Datastore specific field, tldStr, when the entity is loaded from Cloud SQL */
|
||||||
|
@PostLoad
|
||||||
|
void postLoad() {
|
||||||
|
tldStr = tldStrId;
|
||||||
|
}
|
||||||
|
|
||||||
/** The suffix that identifies roids as belonging to this specific tld, e.g. -HOW for .how. */
|
/** The suffix that identifies roids as belonging to this specific tld, e.g. -HOW for .how. */
|
||||||
String roidSuffix;
|
String roidSuffix;
|
||||||
|
@ -289,6 +306,7 @@ public class Registry extends ImmutableObject implements Buildable {
|
||||||
* <p>All entries of this list must be valid keys for the map of {@code DnsWriter}s injected by
|
* <p>All entries of this list must be valid keys for the map of {@code DnsWriter}s injected by
|
||||||
* <code>@Inject Map<String, DnsWriter></code>
|
* <code>@Inject Map<String, DnsWriter></code>
|
||||||
*/
|
*/
|
||||||
|
@Column(nullable = false)
|
||||||
Set<String> dnsWriters;
|
Set<String> dnsWriters;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -312,6 +330,7 @@ public class Registry extends ImmutableObject implements Buildable {
|
||||||
* <p>Failure to do so can result in parallel writes to the {@link
|
* <p>Failure to do so can result in parallel writes to the {@link
|
||||||
* google.registry.dns.writer.DnsWriter}, which may be dangerous depending on your implementation.
|
* google.registry.dns.writer.DnsWriter}, which may be dangerous depending on your implementation.
|
||||||
*/
|
*/
|
||||||
|
@Column(nullable = false)
|
||||||
int numDnsPublishLocks;
|
int numDnsPublishLocks;
|
||||||
|
|
||||||
/** Updates an unset numDnsPublishLocks (0) to the standard default of 1. */
|
/** Updates an unset numDnsPublishLocks (0) to the standard default of 1. */
|
||||||
|
@ -327,6 +346,7 @@ public class Registry extends ImmutableObject implements Buildable {
|
||||||
* <p>This will be equal to {@link #tldStr} for ASCII TLDs, but will be non-ASCII for IDN TLDs. We
|
* <p>This will be equal to {@link #tldStr} for ASCII TLDs, but will be non-ASCII for IDN TLDs. We
|
||||||
* store this in a field so that it will be retained upon import into BigQuery.
|
* store this in a field so that it will be retained upon import into BigQuery.
|
||||||
*/
|
*/
|
||||||
|
@Column(nullable = false)
|
||||||
String tldUnicode;
|
String tldUnicode;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -334,9 +354,11 @@ public class Registry extends ImmutableObject implements Buildable {
|
||||||
*
|
*
|
||||||
* <p>This is optional; if not configured, then information won't be exported for this TLD.
|
* <p>This is optional; if not configured, then information won't be exported for this TLD.
|
||||||
*/
|
*/
|
||||||
String driveFolderId;
|
@Nullable String driveFolderId;
|
||||||
|
|
||||||
/** The type of the TLD, whether it's real or for testing. */
|
/** The type of the TLD, whether it's real or for testing. */
|
||||||
|
@Column(nullable = false)
|
||||||
|
@Enumerated(EnumType.STRING)
|
||||||
TldType tldType = TldType.REAL;
|
TldType tldType = TldType.REAL;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -345,20 +367,24 @@ public class Registry extends ImmutableObject implements Buildable {
|
||||||
* <p>Note that this boolean is the sole determiner on whether invoices should be generated for a
|
* <p>Note that this boolean is the sole determiner on whether invoices should be generated for a
|
||||||
* TLD. This applies to {@link TldType#TEST} TLDs as well.
|
* TLD. This applies to {@link TldType#TEST} TLDs as well.
|
||||||
*/
|
*/
|
||||||
|
@Column(nullable = false)
|
||||||
boolean invoicingEnabled = false;
|
boolean invoicingEnabled = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A property that transitions to different TldStates at different times. Stored as a list of
|
* A property that transitions to different TldStates at different times. Stored as a list of
|
||||||
* TldStateTransition embedded objects using the @Mapify annotation.
|
* TldStateTransition embedded objects using the @Mapify annotation.
|
||||||
*/
|
*/
|
||||||
|
@Column(nullable = false)
|
||||||
@Mapify(TimedTransitionProperty.TimeMapper.class)
|
@Mapify(TimedTransitionProperty.TimeMapper.class)
|
||||||
TimedTransitionProperty<TldState, TldStateTransition> tldStateTransitions =
|
TimedTransitionProperty<TldState, TldStateTransition> tldStateTransitions =
|
||||||
TimedTransitionProperty.forMapify(DEFAULT_TLD_STATE, TldStateTransition.class);
|
TimedTransitionProperty.forMapify(DEFAULT_TLD_STATE, TldStateTransition.class);
|
||||||
|
|
||||||
/** An automatically managed creation timestamp. */
|
/** An automatically managed creation timestamp. */
|
||||||
|
@Column(nullable = false)
|
||||||
CreateAutoTimestamp creationTime = CreateAutoTimestamp.create(null);
|
CreateAutoTimestamp creationTime = CreateAutoTimestamp.create(null);
|
||||||
|
|
||||||
/** The set of reserved lists that are applicable to this registry. */
|
/** The set of reserved lists that are applicable to this registry. */
|
||||||
|
@Column(name = "reserved_list_names", nullable = false)
|
||||||
Set<Key<ReservedList>> reservedLists;
|
Set<Key<ReservedList>> reservedLists;
|
||||||
|
|
||||||
/** Retrieves an ImmutableSet of all ReservedLists associated with this tld. */
|
/** Retrieves an ImmutableSet of all ReservedLists associated with this tld. */
|
||||||
|
@ -367,12 +393,15 @@ public class Registry extends ImmutableObject implements Buildable {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The static {@link PremiumList} for this TLD, if there is one. */
|
/** The static {@link PremiumList} for this TLD, if there is one. */
|
||||||
|
@Column(name = "premium_list_name", nullable = true)
|
||||||
Key<PremiumList> premiumList;
|
Key<PremiumList> premiumList;
|
||||||
|
|
||||||
/** Should RDE upload a nightly escrow deposit for this TLD? */
|
/** Should RDE upload a nightly escrow deposit for this TLD? */
|
||||||
|
@Column(nullable = false)
|
||||||
boolean escrowEnabled = DEFAULT_ESCROW_ENABLED;
|
boolean escrowEnabled = DEFAULT_ESCROW_ENABLED;
|
||||||
|
|
||||||
/** Whether the pull queue that writes to authoritative DNS is paused for this TLD. */
|
/** Whether the pull queue that writes to authoritative DNS is paused for this TLD. */
|
||||||
|
@Column(nullable = false)
|
||||||
boolean dnsPaused = DEFAULT_DNS_PAUSED;
|
boolean dnsPaused = DEFAULT_DNS_PAUSED;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -381,39 +410,72 @@ public class Registry extends ImmutableObject implements Buildable {
|
||||||
* <p>Domain deletes are free and effective immediately so long as they take place within this
|
* <p>Domain deletes are free and effective immediately so long as they take place within this
|
||||||
* amount of time following creation.
|
* amount of time following creation.
|
||||||
*/
|
*/
|
||||||
|
@Column(nullable = false)
|
||||||
Duration addGracePeriodLength = DEFAULT_ADD_GRACE_PERIOD;
|
Duration addGracePeriodLength = DEFAULT_ADD_GRACE_PERIOD;
|
||||||
|
|
||||||
/** The length of the anchor tenant add grace period for this TLD. */
|
/** The length of the anchor tenant add grace period for this TLD. */
|
||||||
|
@Column(nullable = false)
|
||||||
Duration anchorTenantAddGracePeriodLength = DEFAULT_ANCHOR_TENANT_ADD_GRACE_PERIOD;
|
Duration anchorTenantAddGracePeriodLength = DEFAULT_ANCHOR_TENANT_ADD_GRACE_PERIOD;
|
||||||
|
|
||||||
/** The length of the auto renew grace period for this TLD. */
|
/** The length of the auto renew grace period for this TLD. */
|
||||||
|
@Column(nullable = false)
|
||||||
Duration autoRenewGracePeriodLength = DEFAULT_AUTO_RENEW_GRACE_PERIOD;
|
Duration autoRenewGracePeriodLength = DEFAULT_AUTO_RENEW_GRACE_PERIOD;
|
||||||
|
|
||||||
/** The length of the redemption grace period for this TLD. */
|
/** The length of the redemption grace period for this TLD. */
|
||||||
|
@Column(nullable = false)
|
||||||
Duration redemptionGracePeriodLength = DEFAULT_REDEMPTION_GRACE_PERIOD;
|
Duration redemptionGracePeriodLength = DEFAULT_REDEMPTION_GRACE_PERIOD;
|
||||||
|
|
||||||
/** The length of the renew grace period for this TLD. */
|
/** The length of the renew grace period for this TLD. */
|
||||||
|
@Column(nullable = false)
|
||||||
Duration renewGracePeriodLength = DEFAULT_RENEW_GRACE_PERIOD;
|
Duration renewGracePeriodLength = DEFAULT_RENEW_GRACE_PERIOD;
|
||||||
|
|
||||||
/** The length of the transfer grace period for this TLD. */
|
/** The length of the transfer grace period for this TLD. */
|
||||||
|
@Column(nullable = false)
|
||||||
Duration transferGracePeriodLength = DEFAULT_TRANSFER_GRACE_PERIOD;
|
Duration transferGracePeriodLength = DEFAULT_TRANSFER_GRACE_PERIOD;
|
||||||
|
|
||||||
/** The length of time before a transfer is automatically approved for this TLD. */
|
/** The length of time before a transfer is automatically approved for this TLD. */
|
||||||
|
@Column(nullable = false)
|
||||||
Duration automaticTransferLength = DEFAULT_AUTOMATIC_TRANSFER_LENGTH;
|
Duration automaticTransferLength = DEFAULT_AUTOMATIC_TRANSFER_LENGTH;
|
||||||
|
|
||||||
/** The length of time a domain spends in the non-redeemable pending delete phase for this TLD. */
|
/** The length of time a domain spends in the non-redeemable pending delete phase for this TLD. */
|
||||||
|
@Column(nullable = false)
|
||||||
Duration pendingDeleteLength = DEFAULT_PENDING_DELETE_LENGTH;
|
Duration pendingDeleteLength = DEFAULT_PENDING_DELETE_LENGTH;
|
||||||
|
|
||||||
/** The currency unit for all costs associated with this TLD. */
|
/** The currency unit for all costs associated with this TLD. */
|
||||||
|
@Column(nullable = false)
|
||||||
CurrencyUnit currency = DEFAULT_CURRENCY;
|
CurrencyUnit currency = DEFAULT_CURRENCY;
|
||||||
|
|
||||||
/** The per-year billing cost for registering a new domain name. */
|
/** The per-year billing cost for registering a new domain name. */
|
||||||
|
@AttributeOverrides({
|
||||||
|
@AttributeOverride(
|
||||||
|
name = "money.amount",
|
||||||
|
column = @Column(name = "create_billing_cost_amount")),
|
||||||
|
@AttributeOverride(
|
||||||
|
name = "money.currency",
|
||||||
|
column = @Column(name = "create_billing_cost_currency"))
|
||||||
|
})
|
||||||
Money createBillingCost = DEFAULT_CREATE_BILLING_COST;
|
Money createBillingCost = DEFAULT_CREATE_BILLING_COST;
|
||||||
|
|
||||||
/** The one-time billing cost for restoring a domain name from the redemption grace period. */
|
/** The one-time billing cost for restoring a domain name from the redemption grace period. */
|
||||||
|
@AttributeOverrides({
|
||||||
|
@AttributeOverride(
|
||||||
|
name = "money.amount",
|
||||||
|
column = @Column(name = "restore_billing_cost_amount")),
|
||||||
|
@AttributeOverride(
|
||||||
|
name = "money.currency",
|
||||||
|
column = @Column(name = "restore_billing_cost_currency"))
|
||||||
|
})
|
||||||
Money restoreBillingCost = DEFAULT_RESTORE_BILLING_COST;
|
Money restoreBillingCost = DEFAULT_RESTORE_BILLING_COST;
|
||||||
|
|
||||||
/** The one-time billing cost for changing the server status (i.e. lock). */
|
/** The one-time billing cost for changing the server status (i.e. lock). */
|
||||||
|
@AttributeOverrides({
|
||||||
|
@AttributeOverride(
|
||||||
|
name = "money.amount",
|
||||||
|
column = @Column(name = "server_status_change_billing_cost_amount")),
|
||||||
|
@AttributeOverride(
|
||||||
|
name = "money.currency",
|
||||||
|
column = @Column(name = "server_status_change_billing_cost_currency"))
|
||||||
|
})
|
||||||
Money serverStatusChangeBillingCost = DEFAULT_SERVER_STATUS_CHANGE_BILLING_COST;
|
Money serverStatusChangeBillingCost = DEFAULT_SERVER_STATUS_CHANGE_BILLING_COST;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -424,11 +486,13 @@ public class Registry extends ImmutableObject implements Buildable {
|
||||||
* name. This cost is also used to compute costs for transfers, since each transfer includes a
|
* name. This cost is also used to compute costs for transfers, since each transfer includes a
|
||||||
* renewal to ensure transfers have a cost.
|
* renewal to ensure transfers have a cost.
|
||||||
*/
|
*/
|
||||||
|
@Column(nullable = false)
|
||||||
@Mapify(TimedTransitionProperty.TimeMapper.class)
|
@Mapify(TimedTransitionProperty.TimeMapper.class)
|
||||||
TimedTransitionProperty<Money, BillingCostTransition> renewBillingCostTransitions =
|
TimedTransitionProperty<Money, BillingCostTransition> renewBillingCostTransitions =
|
||||||
TimedTransitionProperty.forMapify(DEFAULT_RENEW_BILLING_COST, BillingCostTransition.class);
|
TimedTransitionProperty.forMapify(DEFAULT_RENEW_BILLING_COST, BillingCostTransition.class);
|
||||||
|
|
||||||
/** A property that tracks the EAP fee schedule (if any) for the TLD. */
|
/** A property that tracks the EAP fee schedule (if any) for the TLD. */
|
||||||
|
@Column(nullable = false)
|
||||||
@Mapify(TimedTransitionProperty.TimeMapper.class)
|
@Mapify(TimedTransitionProperty.TimeMapper.class)
|
||||||
TimedTransitionProperty<Money, BillingCostTransition> eapFeeSchedule =
|
TimedTransitionProperty<Money, BillingCostTransition> eapFeeSchedule =
|
||||||
TimedTransitionProperty.forMapify(DEFAULT_EAP_BILLING_COST, BillingCostTransition.class);
|
TimedTransitionProperty.forMapify(DEFAULT_EAP_BILLING_COST, BillingCostTransition.class);
|
||||||
|
@ -437,13 +501,14 @@ public class Registry extends ImmutableObject implements Buildable {
|
||||||
String lordnUsername;
|
String lordnUsername;
|
||||||
|
|
||||||
/** The end of the claims period (at or after this time, claims no longer applies). */
|
/** The end of the claims period (at or after this time, claims no longer applies). */
|
||||||
|
@Column(nullable = false)
|
||||||
DateTime claimsPeriodEnd = END_OF_TIME;
|
DateTime claimsPeriodEnd = END_OF_TIME;
|
||||||
|
|
||||||
/** An allow list of clients allowed to be used on domains on this TLD (ignored if empty). */
|
/** An allow list of clients allowed to be used on domains on this TLD (ignored if empty). */
|
||||||
Set<String> allowedRegistrantContactIds;
|
@Nullable Set<String> allowedRegistrantContactIds;
|
||||||
|
|
||||||
/** An allow list of hosts allowed to be used on domains on this TLD (ignored if empty). */
|
/** An allow list of hosts allowed to be used on domains on this TLD (ignored if empty). */
|
||||||
Set<String> allowedFullyQualifiedHostNames;
|
@Nullable Set<String> allowedFullyQualifiedHostNames;
|
||||||
|
|
||||||
public String getTldStr() {
|
public String getTldStr() {
|
||||||
return tldStr;
|
return tldStr;
|
||||||
|
|
|
@ -18,20 +18,19 @@ import static google.registry.model.common.EntityGroupRoot.getCrossTldKey;
|
||||||
|
|
||||||
import com.googlecode.objectify.Key;
|
import com.googlecode.objectify.Key;
|
||||||
import google.registry.model.registry.label.ReservedList;
|
import google.registry.model.registry.label.ReservedList;
|
||||||
import javax.persistence.AttributeConverter;
|
|
||||||
import javax.persistence.Converter;
|
import javax.persistence.Converter;
|
||||||
|
|
||||||
/** JPA converter for a {@link Key} containing a {@link ReservedList} */
|
/** JPA converter for a set of {@link Key} containing a {@link ReservedList} */
|
||||||
@Converter(autoApply = true)
|
@Converter(autoApply = true)
|
||||||
public class ReservedListKeyConverter implements AttributeConverter<Key<ReservedList>, String> {
|
public class ReservedListKeySetConverter extends StringSetConverterBase<Key<ReservedList>> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String convertToDatabaseColumn(Key<ReservedList> attribute) {
|
String toString(Key<ReservedList> key) {
|
||||||
return (attribute == null) ? null : attribute.getName();
|
return key.getName();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Key<ReservedList> convertToEntityAttribute(String dbData) {
|
Key<ReservedList> fromString(String value) {
|
||||||
return (dbData == null) ? null : Key.create(getCrossTldKey(), ReservedList.class, dbData);
|
return Key.create(getCrossTldKey(), ReservedList.class, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -51,6 +51,7 @@
|
||||||
<class>google.registry.model.registrar.Registrar</class>
|
<class>google.registry.model.registrar.Registrar</class>
|
||||||
<class>google.registry.model.registrar.RegistrarContact</class>
|
<class>google.registry.model.registrar.RegistrarContact</class>
|
||||||
<class>google.registry.model.registry.label.PremiumList</class>
|
<class>google.registry.model.registry.label.PremiumList</class>
|
||||||
|
<class>google.registry.model.registry.Registry</class>
|
||||||
<class>google.registry.model.reporting.Spec11ThreatMatch</class>
|
<class>google.registry.model.reporting.Spec11ThreatMatch</class>
|
||||||
<class>google.registry.persistence.transaction.TransactionEntity</class>
|
<class>google.registry.persistence.transaction.TransactionEntity</class>
|
||||||
<class>google.registry.model.tmch.ClaimsListShard</class>
|
<class>google.registry.model.tmch.ClaimsListShard</class>
|
||||||
|
@ -81,7 +82,7 @@
|
||||||
<class>google.registry.persistence.converter.PostalInfoChoiceListConverter</class>
|
<class>google.registry.persistence.converter.PostalInfoChoiceListConverter</class>
|
||||||
<class>google.registry.persistence.converter.PremiumListKeyConverter</class>
|
<class>google.registry.persistence.converter.PremiumListKeyConverter</class>
|
||||||
<class>google.registry.persistence.converter.RegistrarPocSetConverter</class>
|
<class>google.registry.persistence.converter.RegistrarPocSetConverter</class>
|
||||||
<class>google.registry.persistence.converter.ReservedListKeyConverter</class>
|
<class>google.registry.persistence.converter.ReservedListKeySetConverter</class>
|
||||||
<class>google.registry.persistence.converter.Spec11ThreatMatchThreatTypeSetConverter</class>
|
<class>google.registry.persistence.converter.Spec11ThreatMatchThreatTypeSetConverter</class>
|
||||||
<class>google.registry.persistence.converter.StatusValueSetConverter</class>
|
<class>google.registry.persistence.converter.StatusValueSetConverter</class>
|
||||||
<class>google.registry.persistence.converter.StringListConverter</class>
|
<class>google.registry.persistence.converter.StringListConverter</class>
|
||||||
|
|
|
@ -23,6 +23,7 @@ import static google.registry.model.registry.Registry.TldState.GENERAL_AVAILABIL
|
||||||
import static google.registry.model.registry.Registry.TldState.PREDELEGATION;
|
import static google.registry.model.registry.Registry.TldState.PREDELEGATION;
|
||||||
import static google.registry.model.registry.Registry.TldState.QUIET_PERIOD;
|
import static google.registry.model.registry.Registry.TldState.QUIET_PERIOD;
|
||||||
import static google.registry.model.registry.Registry.TldState.START_DATE_SUNRISE;
|
import static google.registry.model.registry.Registry.TldState.START_DATE_SUNRISE;
|
||||||
|
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
|
||||||
import static google.registry.testing.DatastoreHelper.createTld;
|
import static google.registry.testing.DatastoreHelper.createTld;
|
||||||
import static google.registry.testing.DatastoreHelper.newRegistry;
|
import static google.registry.testing.DatastoreHelper.newRegistry;
|
||||||
import static google.registry.testing.DatastoreHelper.persistPremiumList;
|
import static google.registry.testing.DatastoreHelper.persistPremiumList;
|
||||||
|
@ -43,20 +44,40 @@ import google.registry.model.registry.Registry.RegistryNotFoundException;
|
||||||
import google.registry.model.registry.Registry.TldState;
|
import google.registry.model.registry.Registry.TldState;
|
||||||
import google.registry.model.registry.label.PremiumList;
|
import google.registry.model.registry.label.PremiumList;
|
||||||
import google.registry.model.registry.label.ReservedList;
|
import google.registry.model.registry.label.ReservedList;
|
||||||
|
import google.registry.persistence.VKey;
|
||||||
|
import google.registry.persistence.transaction.JpaTestRules;
|
||||||
|
import google.registry.persistence.transaction.JpaTestRules.JpaIntegrationWithCoverageExtension;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import org.joda.money.Money;
|
import org.joda.money.Money;
|
||||||
import org.joda.time.DateTime;
|
import org.joda.time.DateTime;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||||
|
|
||||||
/** Unit tests for {@link Registry}. */
|
/** Unit tests for {@link Registry}. */
|
||||||
class RegistryTest extends EntityTestCase {
|
public class RegistryTest extends EntityTestCase {
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void beforeEach() {
|
void beforeEach() {
|
||||||
createTld("tld");
|
createTld("tld");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@RegisterExtension
|
||||||
|
JpaIntegrationWithCoverageExtension jpa =
|
||||||
|
new JpaTestRules.Builder().withClock(fakeClock).buildIntegrationWithCoverageExtension();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCloudSqlPersistence() {
|
||||||
|
ReservedList rl15 = persistReservedList("tld-reserved15", "potato,FULLY_BLOCKED");
|
||||||
|
PremiumList pl = persistPremiumList("tld2", "lol,USD 50", "cat,USD 700");
|
||||||
|
Registry registry =
|
||||||
|
Registry.get("tld").asBuilder().setReservedLists(rl15).setPremiumList(pl).build();
|
||||||
|
jpaTm().transact(() -> jpaTm().saveNew(registry));
|
||||||
|
Registry persisted =
|
||||||
|
jpaTm().transact(() -> jpaTm().load(VKey.createSql(Registry.class, registry.tldStrId)));
|
||||||
|
assertThat(persisted).isEqualTo(registry);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testPersistence() {
|
void testPersistence() {
|
||||||
assertWithMessage("Registry not found").that(Registry.get("tld")).isNotNull();
|
assertWithMessage("Registry not found").that(Registry.get("tld")).isNotNull();
|
||||||
|
|
|
@ -1,90 +0,0 @@
|
||||||
// Copyright 2020 The Nomulus Authors. All Rights Reserved.
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package google.registry.persistence.converter;
|
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
|
||||||
import static google.registry.model.common.EntityGroupRoot.getCrossTldKey;
|
|
||||||
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
|
|
||||||
|
|
||||||
import com.googlecode.objectify.Key;
|
|
||||||
import google.registry.model.ImmutableObject;
|
|
||||||
import google.registry.model.registry.label.ReservedList;
|
|
||||||
import google.registry.testing.AppEngineExtension;
|
|
||||||
import javax.persistence.Entity;
|
|
||||||
import javax.persistence.Id;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
|
||||||
|
|
||||||
/** Unit tests for {@link ReservedListKeyConverter}. */
|
|
||||||
class ReservedListKeyConverterTest {
|
|
||||||
|
|
||||||
@RegisterExtension
|
|
||||||
final AppEngineExtension appEngine =
|
|
||||||
AppEngineExtension.builder()
|
|
||||||
.withDatastoreAndCloudSql()
|
|
||||||
.withJpaUnitTestEntities(ReservedListEntity.class)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
private final ReservedListKeyConverter converter = new ReservedListKeyConverter();
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void convertToDatabaseColumn_returnsNullIfInputIsNull() {
|
|
||||||
assertThat(converter.convertToDatabaseColumn(null)).isNull();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void convertToDatabaseColumn_convertsCorrectly() {
|
|
||||||
assertThat(
|
|
||||||
converter.convertToDatabaseColumn(
|
|
||||||
Key.create(getCrossTldKey(), ReservedList.class, "testList")))
|
|
||||||
.isEqualTo("testList");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void convertToEntityAttribute_returnsNullIfInputIsNull() {
|
|
||||||
assertThat(converter.convertToEntityAttribute(null)).isNull();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void convertToEntityAttribute_convertsCorrectly() {
|
|
||||||
assertThat(converter.convertToEntityAttribute("testList"))
|
|
||||||
.isEqualTo(Key.create(getCrossTldKey(), ReservedList.class, "testList"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testRoundTrip() {
|
|
||||||
Key<ReservedList> key = Key.create(getCrossTldKey(), ReservedList.class, "test");
|
|
||||||
ReservedListEntity testEntity = new ReservedListEntity(key);
|
|
||||||
jpaTm().transact(() -> jpaTm().getEntityManager().persist(testEntity));
|
|
||||||
ReservedListEntity persisted =
|
|
||||||
jpaTm().transact(() -> jpaTm().getEntityManager().find(ReservedListEntity.class, "test"));
|
|
||||||
assertThat(persisted.reservedList).isEqualTo(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Entity(name = "ReservedListEntity")
|
|
||||||
private static class ReservedListEntity extends ImmutableObject {
|
|
||||||
|
|
||||||
@Id String name;
|
|
||||||
|
|
||||||
Key<ReservedList> reservedList;
|
|
||||||
|
|
||||||
public ReservedListEntity() {}
|
|
||||||
|
|
||||||
ReservedListEntity(Key<ReservedList> reservedList) {
|
|
||||||
this.name = reservedList.getName();
|
|
||||||
this.reservedList = reservedList;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,87 @@
|
||||||
|
// Copyright 2020 The Nomulus Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package google.registry.persistence.converter;
|
||||||
|
|
||||||
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
import static google.registry.model.common.EntityGroupRoot.getCrossTldKey;
|
||||||
|
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
|
||||||
|
|
||||||
|
import com.googlecode.objectify.Key;
|
||||||
|
import google.registry.model.ImmutableObject;
|
||||||
|
import google.registry.model.registry.label.ReservedList;
|
||||||
|
import google.registry.testing.AppEngineExtension;
|
||||||
|
import java.util.Set;
|
||||||
|
import javax.persistence.Entity;
|
||||||
|
import javax.persistence.Id;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||||
|
import org.testcontainers.shaded.com.google.common.collect.ImmutableSet;
|
||||||
|
|
||||||
|
/** Unit tests for {@link ReservedListKeySetConverter}. */
|
||||||
|
class ReservedListKeySetConverterTest {
|
||||||
|
|
||||||
|
@RegisterExtension
|
||||||
|
final AppEngineExtension appEngine =
|
||||||
|
AppEngineExtension.builder()
|
||||||
|
.withDatastoreAndCloudSql()
|
||||||
|
.withJpaUnitTestEntities(ReservedListSetEntity.class)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void roundTripConversion_returnsSameSet() {
|
||||||
|
Key<ReservedList> key1 = Key.create(getCrossTldKey(), ReservedList.class, "test1");
|
||||||
|
Key<ReservedList> key2 = Key.create(getCrossTldKey(), ReservedList.class, "test2");
|
||||||
|
Key<ReservedList> key3 = Key.create(getCrossTldKey(), ReservedList.class, "test3");
|
||||||
|
|
||||||
|
Set<Key<ReservedList>> reservedLists = ImmutableSet.of(key1, key2, key3);
|
||||||
|
ReservedListSetEntity testEntity = new ReservedListSetEntity(reservedLists);
|
||||||
|
jpaTm().transact(() -> jpaTm().getEntityManager().persist(testEntity));
|
||||||
|
ReservedListSetEntity persisted =
|
||||||
|
jpaTm().transact(() -> jpaTm().getEntityManager().find(ReservedListSetEntity.class, "id"));
|
||||||
|
assertThat(persisted.reservedList).containsExactly(key1, key2, key3);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testNullValue_writesAndReadsNullSuccessfully() {
|
||||||
|
ReservedListSetEntity testEntity = new ReservedListSetEntity(null);
|
||||||
|
jpaTm().transact(() -> jpaTm().getEntityManager().persist(testEntity));
|
||||||
|
ReservedListSetEntity persisted =
|
||||||
|
jpaTm().transact(() -> jpaTm().getEntityManager().find(ReservedListSetEntity.class, "id"));
|
||||||
|
assertThat(persisted.reservedList).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testEmptyCollection_writesAndReadsEmptyCollectionSuccessfully() {
|
||||||
|
ReservedListSetEntity testEntity = new ReservedListSetEntity(ImmutableSet.of());
|
||||||
|
jpaTm().transact(() -> jpaTm().getEntityManager().persist(testEntity));
|
||||||
|
ReservedListSetEntity persisted =
|
||||||
|
jpaTm().transact(() -> jpaTm().getEntityManager().find(ReservedListSetEntity.class, "id"));
|
||||||
|
assertThat(persisted.reservedList).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity(name = "ReservedListSetEntity")
|
||||||
|
private static class ReservedListSetEntity extends ImmutableObject {
|
||||||
|
|
||||||
|
@Id String name = "id";
|
||||||
|
|
||||||
|
Set<Key<ReservedList>> reservedList;
|
||||||
|
|
||||||
|
public ReservedListSetEntity() {}
|
||||||
|
|
||||||
|
ReservedListSetEntity(Set<Key<ReservedList>> reservedList) {
|
||||||
|
this.reservedList = reservedList;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -25,6 +25,7 @@ import google.registry.model.history.DomainHistoryTest;
|
||||||
import google.registry.model.history.HostHistoryTest;
|
import google.registry.model.history.HostHistoryTest;
|
||||||
import google.registry.model.poll.PollMessageTest;
|
import google.registry.model.poll.PollMessageTest;
|
||||||
import google.registry.model.registry.RegistryLockDaoTest;
|
import google.registry.model.registry.RegistryLockDaoTest;
|
||||||
|
import google.registry.model.registry.RegistryTest;
|
||||||
import google.registry.model.registry.label.ReservedListSqlDaoTest;
|
import google.registry.model.registry.label.ReservedListSqlDaoTest;
|
||||||
import google.registry.model.reporting.Spec11ThreatMatchTest;
|
import google.registry.model.reporting.Spec11ThreatMatchTest;
|
||||||
import google.registry.model.tmch.ClaimsListDaoTest;
|
import google.registry.model.tmch.ClaimsListDaoTest;
|
||||||
|
@ -86,6 +87,7 @@ import org.junit.runner.RunWith;
|
||||||
PollMessageTest.class,
|
PollMessageTest.class,
|
||||||
PremiumListDaoTest.class,
|
PremiumListDaoTest.class,
|
||||||
RegistrarDaoTest.class,
|
RegistrarDaoTest.class,
|
||||||
|
RegistryTest.class,
|
||||||
ReservedListSqlDaoTest.class,
|
ReservedListSqlDaoTest.class,
|
||||||
RegistryLockDaoTest.class,
|
RegistryLockDaoTest.class,
|
||||||
Spec11ThreatMatchTest.class,
|
Spec11ThreatMatchTest.class,
|
||||||
|
|
53
db/src/main/resources/sql/flyway/V54__add_tld_table.sql
Normal file
53
db/src/main/resources/sql/flyway/V54__add_tld_table.sql
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
-- Copyright 2020 The Nomulus Authors. All Rights Reserved.
|
||||||
|
--
|
||||||
|
-- Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
-- you may not use this file except in compliance with the License.
|
||||||
|
-- You may obtain a copy of the License at
|
||||||
|
--
|
||||||
|
-- http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
--
|
||||||
|
-- Unless required by applicable law or agreed to in writing, software
|
||||||
|
-- distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
-- See the License for the specific language governing permissions and
|
||||||
|
-- limitations under the License.
|
||||||
|
|
||||||
|
create table "Tld" (
|
||||||
|
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 boolean not null,
|
||||||
|
dns_writers text[] not null,
|
||||||
|
drive_folder_id text,
|
||||||
|
eap_fee_schedule hstore not null,
|
||||||
|
escrow_enabled boolean not null,
|
||||||
|
invoicing_enabled boolean 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,
|
||||||
|
renew_billing_cost_transitions hstore not null,
|
||||||
|
renew_grace_period_length interval not null,
|
||||||
|
reserved_list_names text[] not null,
|
||||||
|
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,
|
||||||
|
primary key (tld_name)
|
||||||
|
);
|
|
@ -580,6 +580,46 @@ create sequence temp_history_id_sequence start 1 increment 50;
|
||||||
primary key (id)
|
primary key (id)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
create table "Tld" (
|
||||||
|
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 boolean not null,
|
||||||
|
dns_writers text[] not null,
|
||||||
|
drive_folder_id text,
|
||||||
|
eap_fee_schedule hstore not null,
|
||||||
|
escrow_enabled boolean not null,
|
||||||
|
invoicing_enabled boolean 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,
|
||||||
|
renew_billing_cost_transitions hstore not null,
|
||||||
|
renew_grace_period_length interval not null,
|
||||||
|
reserved_list_names text[] not null,
|
||||||
|
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,
|
||||||
|
primary key (tld_name)
|
||||||
|
);
|
||||||
|
|
||||||
create table "Transaction" (
|
create table "Transaction" (
|
||||||
id bigserial not null,
|
id bigserial not null,
|
||||||
contents bytea,
|
contents bytea,
|
||||||
|
|
|
@ -889,6 +889,50 @@ CREATE SEQUENCE public."SafeBrowsingThreat_id_seq"
|
||||||
ALTER SEQUENCE public."SafeBrowsingThreat_id_seq" OWNED BY public."Spec11ThreatMatch".id;
|
ALTER SEQUENCE public."SafeBrowsingThreat_id_seq" OWNED BY public."Spec11ThreatMatch".id;
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: Tld; Type: TABLE; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE public."Tld" (
|
||||||
|
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 timestamp with time zone NOT NULL,
|
||||||
|
create_billing_cost_amount numeric(19,2),
|
||||||
|
create_billing_cost_currency text,
|
||||||
|
creation_time timestamp with time zone NOT NULL,
|
||||||
|
currency text NOT NULL,
|
||||||
|
dns_paused boolean NOT NULL,
|
||||||
|
dns_writers text[] NOT NULL,
|
||||||
|
drive_folder_id text,
|
||||||
|
eap_fee_schedule public.hstore NOT NULL,
|
||||||
|
escrow_enabled boolean NOT NULL,
|
||||||
|
invoicing_enabled boolean NOT NULL,
|
||||||
|
lordn_username text,
|
||||||
|
num_dns_publish_locks integer NOT NULL,
|
||||||
|
pending_delete_length interval NOT NULL,
|
||||||
|
premium_list_name text,
|
||||||
|
pricing_engine_class_name text,
|
||||||
|
redemption_grace_period_length interval NOT NULL,
|
||||||
|
renew_billing_cost_transitions public.hstore NOT NULL,
|
||||||
|
renew_grace_period_length interval NOT NULL,
|
||||||
|
reserved_list_names text[] NOT NULL,
|
||||||
|
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 public.hstore NOT NULL,
|
||||||
|
tld_type text NOT NULL,
|
||||||
|
tld_unicode text NOT NULL,
|
||||||
|
transfer_grace_period_length interval NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Name: Transaction; Type: TABLE; Schema: public; Owner: -
|
-- Name: Transaction; Type: TABLE; Schema: public; Owner: -
|
||||||
--
|
--
|
||||||
|
@ -1211,6 +1255,14 @@ ALTER TABLE ONLY public."Spec11ThreatMatch"
|
||||||
ADD CONSTRAINT "SafeBrowsingThreat_pkey" PRIMARY KEY (id);
|
ADD CONSTRAINT "SafeBrowsingThreat_pkey" PRIMARY KEY (id);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: Tld Tld_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
ALTER TABLE ONLY public."Tld"
|
||||||
|
ADD CONSTRAINT "Tld_pkey" PRIMARY KEY (tld_name);
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Name: Transaction Transaction_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
-- Name: Transaction Transaction_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
||||||
--
|
--
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue