diff --git a/core/src/main/java/google/registry/schema/tld/PremiumList.java b/core/src/main/java/google/registry/schema/tld/PremiumList.java
new file mode 100644
index 000000000..18da33bfa
--- /dev/null
+++ b/core/src/main/java/google/registry/schema/tld/PremiumList.java
@@ -0,0 +1,102 @@
+// Copyright 2019 The Nomulus Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package google.registry.schema.tld;
+
+import static com.google.common.base.Preconditions.checkState;
+
+import java.math.BigDecimal;
+import java.time.ZonedDateTime;
+import java.util.Map;
+import javax.persistence.CollectionTable;
+import javax.persistence.Column;
+import javax.persistence.ElementCollection;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.MapKeyColumn;
+import javax.persistence.Table;
+import org.joda.money.CurrencyUnit;
+
+/**
+ * A list of premium prices for domain names.
+ *
+ *
Note that the primary key of this entity is {@link #revisionId}, which is auto-generated by
+ * the database. So, if a retry of insertion happens after the previous attempt unexpectedly
+ * succeeds, we will end up with having two exact same premium lists that differ only by revisionId.
+ * This is fine though, because we only use the list with the highest revisionId.
+ */
+@Entity
+@Table(name = "PremiumList")
+public class PremiumList {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Column(name = "revision_id")
+ private Long revisionId;
+
+ @Column(name = "creation_timestamp", nullable = false)
+ private ZonedDateTime creationTimestamp;
+
+ @Column(name = "currency", nullable = false)
+ private CurrencyUnit currency;
+
+ @ElementCollection
+ @CollectionTable(
+ name = "PremiumEntry",
+ joinColumns = @JoinColumn(name = "revision_id", referencedColumnName = "revision_id"))
+ @MapKeyColumn(name = "domain_label")
+ @Column(name = "price", nullable = false)
+ private Map labelsToPrices;
+
+ private PremiumList(
+ ZonedDateTime creationTimestamp,
+ CurrencyUnit currency,
+ Map labelsToPrices) {
+ this.creationTimestamp = creationTimestamp;
+ this.currency = currency;
+ this.labelsToPrices = labelsToPrices;
+ }
+
+ // Hibernate requires this default constructor.
+ private PremiumList() {}
+
+ // TODO(mcilwain): Change creationTimestamp to Joda DateTime.
+ /** Constructs a {@link PremiumList} object. */
+ public static PremiumList create(
+ ZonedDateTime creationTimestamp,
+ CurrencyUnit currency,
+ Map labelsToPrices) {
+ return new PremiumList(creationTimestamp, currency, labelsToPrices);
+ }
+
+ /** Returns the ID of this revision, or throws if null. */
+ public Long getRevisionId() {
+ checkState(
+ revisionId != null, "revisionId is null because it is not persisted in the database");
+ return revisionId;
+ }
+
+ /** Returns the creation time of this revision of the premium list. */
+ public ZonedDateTime getCreationTimestamp() {
+ return creationTimestamp;
+ }
+
+ /** Returns a {@link Map} of domain labels to prices. */
+ public Map getLabelsToPrices() {
+ return labelsToPrices;
+ }
+}
diff --git a/core/src/main/java/google/registry/tools/GenerateSqlSchemaCommand.java b/core/src/main/java/google/registry/tools/GenerateSqlSchemaCommand.java
index 425d2d244..8541ccb7a 100644
--- a/core/src/main/java/google/registry/tools/GenerateSqlSchemaCommand.java
+++ b/core/src/main/java/google/registry/tools/GenerateSqlSchemaCommand.java
@@ -19,6 +19,7 @@ import static java.nio.charset.StandardCharsets.UTF_8;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableSet;
import google.registry.model.domain.DesignatedContact;
import google.registry.model.domain.DomainBase;
import google.registry.model.domain.GracePeriod;
@@ -26,6 +27,8 @@ import google.registry.model.domain.secdns.DelegationSignerData;
import google.registry.model.eppcommon.Trid;
import google.registry.model.transfer.BaseTransferObject;
import google.registry.model.transfer.TransferData;
+import google.registry.schema.tld.PremiumList;
+import google.registry.schema.tmch.ClaimsList;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
@@ -49,6 +52,21 @@ import org.testcontainers.containers.PostgreSQLContainer;
@Parameters(separators = " =", commandDescription = "Generate PostgreSQL schema.")
public class GenerateSqlSchemaCommand implements Command {
+ // TODO(mmuller): These should be read from persistence.xml so we don't need to maintain two
+ // separate lists of all SQL table classes.
+ private static final ImmutableSet SQL_TABLE_CLASSES =
+ ImmutableSet.of(
+ BaseTransferObject.class,
+ ClaimsList.class,
+ DelegationSignerData.class,
+ DesignatedContact.class,
+ DomainBase.class,
+ GracePeriod.class,
+ Period.class,
+ PremiumList.class,
+ TransferData.class,
+ Trid.class);
+
@VisibleForTesting
public static final String DB_OPTIONS_CLASH =
"Database host and port may not be specified along with the option to start a "
@@ -109,8 +127,7 @@ public class GenerateSqlSchemaCommand implements Command {
+ " -d postgres:9.6.12\n\n"
+ "Copy the container id output from the command, then run:\n\n"
+ " docker inspect | grep IPAddress\n\n"
- + "To obtain the value for --db-host.\n"
- );
+ + "To obtain the value for --db-host.\n");
// TODO(mmuller): need exit(1), see above.
return;
}
@@ -134,14 +151,7 @@ public class GenerateSqlSchemaCommand implements Command {
MetadataSources metadata =
new MetadataSources(new StandardServiceRegistryBuilder().applySettings(settings).build());
- metadata.addAnnotatedClass(BaseTransferObject.class);
- metadata.addAnnotatedClass(DelegationSignerData.class);
- metadata.addAnnotatedClass(DesignatedContact.class);
- metadata.addAnnotatedClass(DomainBase.class);
- metadata.addAnnotatedClass(GracePeriod.class);
- metadata.addAnnotatedClass(Period.class);
- metadata.addAnnotatedClass(TransferData.class);
- metadata.addAnnotatedClass(Trid.class);
+ SQL_TABLE_CLASSES.forEach(metadata::addAnnotatedClass);
SchemaExport schemaExport = new SchemaExport();
schemaExport.setHaltOnError(true);
schemaExport.setFormat(true);
diff --git a/core/src/main/resources/META-INF/persistence.xml b/core/src/main/resources/META-INF/persistence.xml
index c01e3f24b..fb8a86acb 100644
--- a/core/src/main/resources/META-INF/persistence.xml
+++ b/core/src/main/resources/META-INF/persistence.xml
@@ -22,6 +22,7 @@
google.registry.model.domain.DomainBase
google.registry.schema.tmch.ClaimsList
google.registry.model.transfer.BaseTransferObject
+ google.registry.schema.tld.PremiumList
google.registry.model.domain.secdns.DelegationSignerData
google.registry.model.domain.DesignatedContact
google.registry.model.domain.DomainBase
diff --git a/core/src/test/java/google/registry/tools/GenerateSqlSchemaCommandTest.java b/core/src/test/java/google/registry/tools/GenerateSqlSchemaCommandTest.java
index 63c0ea6ab..f9f0fb909 100644
--- a/core/src/test/java/google/registry/tools/GenerateSqlSchemaCommandTest.java
+++ b/core/src/test/java/google/registry/tools/GenerateSqlSchemaCommandTest.java
@@ -27,7 +27,6 @@ import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.testcontainers.containers.PostgreSQLContainer;
-
/** Unit tests for {@link google.registry.tools.GenerateSqlSchemaCommand}. */
@RunWith(JUnit4.class)
public class GenerateSqlSchemaCommandTest extends CommandTestCase {
@@ -77,8 +76,7 @@ public class GenerateSqlSchemaCommandTest extends CommandTestCase