diff --git a/core/src/main/java/google/registry/schema/tld/ReservedList.java b/core/src/main/java/google/registry/schema/tld/ReservedList.java
new file mode 100644
index 000000000..c3897d157
--- /dev/null
+++ b/core/src/main/java/google/registry/schema/tld/ReservedList.java
@@ -0,0 +1,147 @@
+// 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 com.google.common.collect.ImmutableMap;
+import google.registry.model.CreateAutoTimestamp;
+import google.registry.model.ImmutableObject;
+import google.registry.model.registry.label.ReservationType;
+import java.util.Map;
+import javax.annotation.Nullable;
+import javax.persistence.CollectionTable;
+import javax.persistence.Column;
+import javax.persistence.ElementCollection;
+import javax.persistence.Embeddable;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Index;
+import javax.persistence.JoinColumn;
+import javax.persistence.MapKeyColumn;
+import javax.persistence.Table;
+import org.joda.time.DateTime;
+
+/**
+ * A list of reserved domain labels that are blocked from being registered for various reasons.
+ *
+ *
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 reserved lists that differ only by
+ * revisionId. This is fine though, because we only use the list with the highest revisionId.
+ */
+@Entity
+@Table(indexes = {@Index(columnList = "name", name = "reservedlist_name_idx")})
+public class ReservedList extends ImmutableObject {
+
+ @Column(nullable = false)
+ private String name;
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Column(nullable = false)
+ private Long revisionId;
+
+ @Column(nullable = false)
+ private CreateAutoTimestamp creationTimestamp = CreateAutoTimestamp.create(null);
+
+ @Column(nullable = false)
+ private Boolean shouldPublish;
+
+ @ElementCollection
+ @CollectionTable(
+ name = "ReservedEntry",
+ joinColumns = @JoinColumn(name = "revisionId", referencedColumnName = "revisionId"))
+ @MapKeyColumn(name = "domainLabel")
+ private Map labelsToReservations;
+
+ @Embeddable
+ public static class ReservedEntry extends ImmutableObject {
+ @Column(nullable = false)
+ private ReservationType reservationType;
+
+ @Column(nullable = true)
+ private String comment;
+
+ private ReservedEntry(ReservationType reservationType, @Nullable String comment) {
+ this.reservationType = reservationType;
+ this.comment = comment;
+ }
+
+ // Hibernate requires this default constructor.
+ private ReservedEntry() {}
+
+ /** Constructs a {@link ReservedEntry} object. */
+ public static ReservedEntry create(ReservationType reservationType, @Nullable String comment) {
+ return new ReservedEntry(reservationType, comment);
+ }
+
+ /** Returns the reservation type for this entry. */
+ public ReservationType getReservationType() {
+ return reservationType;
+ }
+
+ /** Returns the comment for this entry. Retruns null if there is no comment. */
+ public String getComment() {
+ return comment;
+ }
+ }
+
+ private ReservedList(
+ String name, Boolean shouldPublish, Map labelsToReservations) {
+ this.name = name;
+ this.shouldPublish = shouldPublish;
+ this.labelsToReservations = labelsToReservations;
+ }
+
+ // Hibernate requires this default constructor.
+ private ReservedList() {}
+
+ /** Constructs a {@link ReservedList} object. */
+ public static ReservedList create(
+ String name, Boolean shouldPublish, Map labelsToReservations) {
+ return new ReservedList(name, shouldPublish, labelsToReservations);
+ }
+
+ /** Returns the name of the reserved list. */
+ public String getName() {
+ return name;
+ }
+
+ /** Returns the ID of this revision, or throws if null. */
+ public Long getRevisionId() {
+ checkState(
+ revisionId != null,
+ "revisionId is null because this object has not been persisted to the database yet");
+ return revisionId;
+ }
+
+ /** Returns the creation time of this revision of the reserved list. */
+ public DateTime getCreationTimestamp() {
+ return creationTimestamp.getTimestamp();
+ }
+
+ /** Returns a {@link Map} of domain labels to {@link ReservedEntry}. */
+ public ImmutableMap getLabelsToReservations() {
+ return ImmutableMap.copyOf(labelsToReservations);
+ }
+
+ /** Returns true if the reserved list should be published. */
+ public Boolean getShouldPublish() {
+ return shouldPublish;
+ }
+}
diff --git a/core/src/main/resources/META-INF/persistence.xml b/core/src/main/resources/META-INF/persistence.xml
index 10a931a02..47e866d8a 100644
--- a/core/src/main/resources/META-INF/persistence.xml
+++ b/core/src/main/resources/META-INF/persistence.xml
@@ -24,6 +24,7 @@
google.registry.schema.tmch.ClaimsList
google.registry.model.transfer.BaseTransferObject
google.registry.schema.tld.PremiumList
+ google.registry.schema.tld.ReservedList
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/schema/tld/ReservedListTest.java b/core/src/test/java/google/registry/schema/tld/ReservedListTest.java
new file mode 100644
index 000000000..5d3643b37
--- /dev/null
+++ b/core/src/test/java/google/registry/schema/tld/ReservedListTest.java
@@ -0,0 +1,53 @@
+// 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.truth.Truth.assertThat;
+
+import com.google.common.collect.ImmutableMap;
+import google.registry.model.registry.label.ReservationType;
+import google.registry.schema.tld.ReservedList.ReservedEntry;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link ReservedList} */
+@RunWith(JUnit4.class)
+public class ReservedListTest {
+
+ @Test
+ public void verifyConstructorAndGetters_workCorrectly() {
+ ReservedList reservedList =
+ ReservedList.create(
+ "app",
+ false,
+ ImmutableMap.of(
+ "book",
+ ReservedEntry.create(ReservationType.ALLOWED_IN_SUNRISE, null),
+ "music",
+ ReservedEntry.create(
+ ReservationType.RESERVED_FOR_ANCHOR_TENANT, "reserved for anchor tenant")));
+
+ assertThat(reservedList.getName()).isEqualTo("app");
+ assertThat(reservedList.getShouldPublish()).isFalse();
+ assertThat(reservedList.getLabelsToReservations())
+ .containsExactly(
+ "book",
+ ReservedEntry.create(ReservationType.ALLOWED_IN_SUNRISE, null),
+ "music",
+ ReservedEntry.create(
+ ReservationType.RESERVED_FOR_ANCHOR_TENANT, "reserved for anchor tenant"));
+ }
+}
diff --git a/db/src/main/resources/sql/flyway/V10__create_reserved_list_and_entry.sql b/db/src/main/resources/sql/flyway/V10__create_reserved_list_and_entry.sql
new file mode 100644
index 000000000..2fc45f712
--- /dev/null
+++ b/db/src/main/resources/sql/flyway/V10__create_reserved_list_and_entry.sql
@@ -0,0 +1,36 @@
+-- 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.
+
+ create table "ReservedEntry" (
+ revision_id int8 not null,
+ comment text,
+ reservation_type int4 not null,
+ domain_label text not null,
+ primary key (revision_id, domain_label)
+ );
+
+ create table "ReservedList" (
+ revision_id bigserial not null,
+ creation_timestamp timestamptz not null,
+ name text not null,
+ should_publish boolean not null,
+ primary key (revision_id)
+ );
+
+create index reservedlist_name_idx on "ReservedList" (name);
+
+ alter table if exists "ReservedEntry"
+ add constraint FKgq03rk0bt1hb915dnyvd3vnfc
+ foreign key (revision_id)
+ references "ReservedList";
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 63bc7031c..d2bb8ddf3 100644
--- a/db/src/main/resources/sql/schema/db-schema.sql.generated
+++ b/db/src/main/resources/sql/schema/db-schema.sql.generated
@@ -152,6 +152,22 @@
primary key (revision_id)
);
+ create table "ReservedEntry" (
+ revision_id int8 not null,
+ comment text,
+ reservation_type int4 not null,
+ domain_label text not null,
+ primary key (revision_id, domain_label)
+ );
+
+ create table "ReservedList" (
+ revision_id bigserial not null,
+ creation_timestamp timestamptz not null,
+ name text not null,
+ should_publish boolean not null,
+ primary key (revision_id)
+ );
+
alter table if exists "Domain_DelegationSignerData"
add constraint UK_2yp55erx1i51pa7gnb8bu7tjn unique (ds_data_key_tag);
@@ -166,6 +182,7 @@ create index idx_registry_lock_registrar_id on "RegistryLock" (registrar_id);
alter table if exists "RegistryLock"
add constraint idx_registry_lock_repo_id_revision_id unique (repo_id, revision_id);
+create index reservedlist_name_idx on "ReservedList" (name);
alter table if exists "ClaimsEntry"
add constraint FK6sc6at5hedffc0nhdcab6ivuq
@@ -221,3 +238,8 @@ create index idx_registry_lock_registrar_id on "RegistryLock" (registrar_id);
add constraint FKo0gw90lpo1tuee56l0nb6y6g5
foreign key (revision_id)
references "PremiumList";
+
+ alter table if exists "ReservedEntry"
+ add constraint FKgq03rk0bt1hb915dnyvd3vnfc
+ foreign key (revision_id)
+ references "ReservedList";
diff --git a/db/src/main/resources/sql/schema/nomulus.golden.sql b/db/src/main/resources/sql/schema/nomulus.golden.sql
index 5aafd926d..5a21eb19b 100644
--- a/db/src/main/resources/sql/schema/nomulus.golden.sql
+++ b/db/src/main/resources/sql/schema/nomulus.golden.sql
@@ -141,6 +141,49 @@ CREATE SEQUENCE public."RegistryLock_revision_id_seq"
ALTER SEQUENCE public."RegistryLock_revision_id_seq" OWNED BY public."RegistryLock".revision_id;
+--
+-- Name: ReservedEntry; Type: TABLE; Schema: public; Owner: -
+--
+
+CREATE TABLE public."ReservedEntry" (
+ revision_id bigint NOT NULL,
+ comment text,
+ reservation_type integer NOT NULL,
+ domain_label text NOT NULL
+);
+
+
+--
+-- Name: ReservedList; Type: TABLE; Schema: public; Owner: -
+--
+
+CREATE TABLE public."ReservedList" (
+ revision_id bigint NOT NULL,
+ creation_timestamp timestamp with time zone NOT NULL,
+ name text NOT NULL,
+ should_publish boolean NOT NULL
+);
+
+
+--
+-- Name: ReservedList_revision_id_seq; Type: SEQUENCE; Schema: public; Owner: -
+--
+
+CREATE SEQUENCE public."ReservedList_revision_id_seq"
+ START WITH 1
+ INCREMENT BY 1
+ NO MINVALUE
+ NO MAXVALUE
+ CACHE 1;
+
+
+--
+-- Name: ReservedList_revision_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
+--
+
+ALTER SEQUENCE public."ReservedList_revision_id_seq" OWNED BY public."ReservedList".revision_id;
+
+
--
-- Name: ClaimsList revision_id; Type: DEFAULT; Schema: public; Owner: -
--
@@ -162,6 +205,13 @@ ALTER TABLE ONLY public."PremiumList" ALTER COLUMN revision_id SET DEFAULT nextv
ALTER TABLE ONLY public."RegistryLock" ALTER COLUMN revision_id SET DEFAULT nextval('public."RegistryLock_revision_id_seq"'::regclass);
+--
+-- Name: ReservedList revision_id; Type: DEFAULT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY public."ReservedList" ALTER COLUMN revision_id SET DEFAULT nextval('public."ReservedList_revision_id_seq"'::regclass);
+
+
--
-- Name: ClaimsEntry ClaimsEntry_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--
@@ -202,6 +252,22 @@ ALTER TABLE ONLY public."RegistryLock"
ADD CONSTRAINT "RegistryLock_pkey" PRIMARY KEY (revision_id);
+--
+-- Name: ReservedEntry ReservedEntry_pkey; Type: CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY public."ReservedEntry"
+ ADD CONSTRAINT "ReservedEntry_pkey" PRIMARY KEY (revision_id, domain_label);
+
+
+--
+-- Name: ReservedList ReservedList_pkey; Type: CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY public."ReservedList"
+ ADD CONSTRAINT "ReservedList_pkey" PRIMARY KEY (revision_id);
+
+
--
-- Name: RegistryLock idx_registry_lock_repo_id_revision_id; Type: CONSTRAINT; Schema: public; Owner: -
--
@@ -231,6 +297,13 @@ CREATE INDEX idx_registry_lock_verification_code ON public."RegistryLock" USING
CREATE INDEX premiumlist_name_idx ON public."PremiumList" USING btree (name);
+--
+-- Name: reservedlist_name_idx; Type: INDEX; Schema: public; Owner: -
+--
+
+CREATE INDEX reservedlist_name_idx ON public."ReservedList" USING btree (name);
+
+
--
-- Name: ClaimsEntry fk6sc6at5hedffc0nhdcab6ivuq; Type: FK CONSTRAINT; Schema: public; Owner: -
--
@@ -239,6 +312,14 @@ ALTER TABLE ONLY public."ClaimsEntry"
ADD CONSTRAINT fk6sc6at5hedffc0nhdcab6ivuq FOREIGN KEY (revision_id) REFERENCES public."ClaimsList"(revision_id);
+--
+-- Name: ReservedEntry fkgq03rk0bt1hb915dnyvd3vnfc; Type: FK CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY public."ReservedEntry"
+ ADD CONSTRAINT fkgq03rk0bt1hb915dnyvd3vnfc FOREIGN KEY (revision_id) REFERENCES public."ReservedList"(revision_id);
+
+
--
-- Name: PremiumEntry fko0gw90lpo1tuee56l0nb6y6g5; Type: FK CONSTRAINT; Schema: public; Owner: -
--