Add SQL schema and DAO for SignedMarkRevocationList (#850)

* Add SQL schema and DAO for SignedMarkRevocationList

This gets saved every day so we're not concerned about history, meaning
we can dual-write and/or dual-read without concern. The structure here
is somewhat similar to the ClaimsListDao and related classes.

* Update the DB files
This commit is contained in:
gbrodman 2020-10-30 17:52:09 -04:00 committed by GitHub
parent 8bd5eb4eca
commit 40eef2a06c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 2548 additions and 1735 deletions

View file

@ -28,8 +28,12 @@ import static google.registry.util.DateTimeUtils.isBeforeOrAt;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.MapDifference;
import com.google.common.collect.Maps;
import com.google.common.flogger.FluentLogger;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.annotation.EmbedMap;
import com.googlecode.objectify.annotation.Entity;
@ -41,8 +45,19 @@ import google.registry.model.ImmutableObject;
import google.registry.model.annotations.NotBackedUp;
import google.registry.model.annotations.NotBackedUp.Reason;
import google.registry.model.common.EntityGroupRoot;
import google.registry.schema.replay.DatastoreEntity;
import google.registry.schema.replay.SqlEntity;
import google.registry.util.CollectionUtils;
import java.util.Map;
import java.util.Optional;
import javax.persistence.CollectionTable;
import javax.persistence.Column;
import javax.persistence.ElementCollection;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.JoinColumn;
import javax.persistence.MapKeyColumn;
import javax.persistence.Transient;
import org.joda.time.DateTime;
/**
@ -56,35 +71,50 @@ import org.joda.time.DateTime;
* order to avoid exceeding the one megabyte max entity size limit, we'll also be sharding that
* entity into multiple entities, each entity containing {@value #SHARD_SIZE} rows.
*
* <p>TODO: We can remove the sharding once we have converted entirely to Cloud SQL storage during
* the Registry 3.0 migration. Then, the entire table will be stored conceptually as one entity (in
* fact in SignedMarkRevocationList and SignedMarkRevocationEntry tables).
*
* @see google.registry.tmch.SmdrlCsvParser
* @see <a href="http://tools.ietf.org/html/draft-lozano-tmch-func-spec-08#section-6.2">
* TMCH functional specifications - SMD Revocation List</a>
* @see <a href="http://tools.ietf.org/html/draft-lozano-tmch-func-spec-08#section-6.2">TMCH
* functional specifications - SMD Revocation List</a>
*/
@Entity
@javax.persistence.Entity
@NotBackedUp(reason = Reason.EXTERNALLY_SOURCED)
public class SignedMarkRevocationList extends ImmutableObject {
public class SignedMarkRevocationList extends ImmutableObject
implements DatastoreEntity, SqlEntity {
@VisibleForTesting
static final int SHARD_SIZE = 10000;
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
@VisibleForTesting static final int SHARD_SIZE = 10000;
/** Common ancestor for queries. */
@Parent
Key<EntityGroupRoot> parent = getCrossTldKey();
@Parent @Transient Key<EntityGroupRoot> parent = getCrossTldKey();
/** ID for the sharded entity. */
@Id
long id;
@Id @Transient long id;
@Ignore
@javax.persistence.Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Long revisionId;
/** Time when this list was last updated, as specified in the first line of the CSV file. */
DateTime creationTime;
/** A map from SMD IDs to revocation time. */
@EmbedMap
@ElementCollection
@CollectionTable(
name = "SignedMarkRevocationEntry",
joinColumns = @JoinColumn(name = "revisionId", referencedColumnName = "revisionId"))
@MapKeyColumn(name = "smdId")
@Column(name = "revocationTime", nullable = false)
Map</*@MatchesPattern("[0-9]+-[0-9]+")*/ String, DateTime> revokes;
/** Indicates that this is a shard rather than a "full" list. */
@Ignore
boolean isShard;
@Ignore @Transient boolean isShard;
/**
* A cached supplier that fetches the SMDRL shards from Datastore and recombines them into a
@ -92,32 +122,16 @@ public class SignedMarkRevocationList extends ImmutableObject {
*/
private static final Supplier<SignedMarkRevocationList> CACHE =
memoizeWithShortExpiration(
() ->
tm()
.transactNewReadOnly(
() -> {
Iterable<SignedMarkRevocationList> shards =
ofy()
.load()
.type(SignedMarkRevocationList.class)
.ancestor(getCrossTldKey());
DateTime creationTime =
isEmpty(shards)
? START_OF_TIME
: checkNotNull(
Iterables.get(shards, 0).creationTime, "creationTime");
ImmutableMap.Builder<String, DateTime> revokes =
new ImmutableMap.Builder<>();
for (SignedMarkRevocationList shard : shards) {
revokes.putAll(shard.revokes);
checkState(
creationTime.equals(shard.creationTime),
"Inconsistent creation times: %s vs. %s",
creationTime,
shard.creationTime);
SignedMarkRevocationList datastoreList = loadFromDatastore();
// Also load the list from Cloud SQL, compare the two lists, and log if different.
try {
loadAndCompareCloudSqlList(datastoreList);
} catch (Throwable t) {
logger.atSevere().withCause(t).log("Error comparing signed mark revocation lists.");
}
return create(creationTime, revokes.build());
}));
return datastoreList;
});
/** Return a single logical instance that combines all Datastore shards. */
public static SignedMarkRevocationList get() {
@ -149,10 +163,39 @@ public class SignedMarkRevocationList extends ImmutableObject {
return revokes.size();
}
/** Save this list to Datastore in sharded form. Returns {@code this}. */
/** Save this list to Datastore in sharded form and to Cloud SQL. Returns {@code this}. */
public SignedMarkRevocationList save() {
tm()
.transact(
saveToDatastore();
SignedMarkRevocationListDao.trySave(this);
return this;
}
/** Loads the shards from Datastore and combines them into one list. */
private static SignedMarkRevocationList loadFromDatastore() {
return tm().transactNewReadOnly(
() -> {
Iterable<SignedMarkRevocationList> shards =
ofy().load().type(SignedMarkRevocationList.class).ancestor(getCrossTldKey());
DateTime creationTime =
isEmpty(shards)
? START_OF_TIME
: checkNotNull(Iterables.get(shards, 0).creationTime, "creationTime");
ImmutableMap.Builder<String, DateTime> revokes = new ImmutableMap.Builder<>();
for (SignedMarkRevocationList shard : shards) {
revokes.putAll(shard.revokes);
checkState(
creationTime.equals(shard.creationTime),
"Inconsistent creation times: %s vs. %s",
creationTime,
shard.creationTime);
}
return create(creationTime, revokes.build());
});
}
/** Save this list to Datastore in sharded form. */
private SignedMarkRevocationList saveToDatastore() {
tm().transact(
() -> {
ofy()
.deleteWithoutBackup()
@ -165,8 +208,7 @@ public class SignedMarkRevocationList extends ImmutableObject {
ofy()
.saveWithoutBackup()
.entities(
CollectionUtils.partitionMap(revokes, SHARD_SIZE)
.stream()
CollectionUtils.partitionMap(revokes, SHARD_SIZE).stream()
.map(
shardRevokes -> {
SignedMarkRevocationList shard = create(creationTime, shardRevokes);
@ -180,6 +222,38 @@ public class SignedMarkRevocationList extends ImmutableObject {
return this;
}
private static void loadAndCompareCloudSqlList(SignedMarkRevocationList datastoreList) {
// Lifted with some modifications from ClaimsListShard
Optional<SignedMarkRevocationList> maybeCloudSqlList =
SignedMarkRevocationListDao.getLatestRevision();
if (maybeCloudSqlList.isPresent()) {
SignedMarkRevocationList cloudSqlList = maybeCloudSqlList.get();
MapDifference<String, DateTime> diff =
Maps.difference(datastoreList.revokes, cloudSqlList.revokes);
if (!diff.areEqual()) {
if (diff.entriesDiffering().size() > 10) {
logger.atWarning().log(
String.format(
"Unequal SM revocation lists detected, Cloud SQL list with revision id %d has %d"
+ " different records than the current Datastore list.",
cloudSqlList.revisionId, diff.entriesDiffering().size()));
} else {
StringBuilder diffMessage = new StringBuilder("Unequal SM revocation lists detected:\n");
diff.entriesDiffering()
.forEach(
(label, valueDiff) ->
diffMessage.append(
String.format(
"SMD %s has key %s in Datastore and key %s in Cloud SQL.\n",
label, valueDiff.leftValue(), valueDiff.rightValue())));
logger.atWarning().log(diffMessage.toString());
}
}
} else {
logger.atWarning().log("Signed mark revocation list in Cloud SQL is empty.");
}
}
/** As a safety mechanism, fail if someone tries to save this class directly. */
@OnSave
void disallowUnshardedSaves() {
@ -188,6 +262,16 @@ public class SignedMarkRevocationList extends ImmutableObject {
}
}
@Override
public ImmutableList<SqlEntity> toSqlEntities() {
return ImmutableList.of(); // Dually-written every day
}
@Override
public ImmutableList<DatastoreEntity> toDatastoreEntities() {
return ImmutableList.of(); // Dually-written every day
}
/** Exception when trying to directly save a {@link SignedMarkRevocationList} without sharding. */
public static class UnshardedSaveException extends RuntimeException {}
}

View file

@ -0,0 +1,76 @@
// 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.model.smd;
import static google.registry.model.CacheUtils.memoizeWithShortExpiration;
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
import com.google.common.base.Supplier;
import com.google.common.flogger.FluentLogger;
import java.util.Optional;
import javax.persistence.EntityManager;
public class SignedMarkRevocationListDao {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private static final Supplier<Optional<SignedMarkRevocationList>> CACHE =
memoizeWithShortExpiration(SignedMarkRevocationListDao::getLatestRevision);
/** Returns the most recent revision of the {@link SignedMarkRevocationList}, from cache. */
public static Optional<SignedMarkRevocationList> getLatestRevisionCached() {
return CACHE.get();
}
public static Optional<SignedMarkRevocationList> getLatestRevision() {
return jpaTm()
.transact(
() -> {
EntityManager em = jpaTm().getEntityManager();
Long revisionId =
em.createQuery("SELECT MAX(revisionId) FROM SignedMarkRevocationList", Long.class)
.getSingleResult();
return em.createQuery(
"FROM SignedMarkRevocationList smrl LEFT JOIN FETCH smrl.revokes "
+ "WHERE smrl.revisionId = :revisionId",
SignedMarkRevocationList.class)
.setParameter("revisionId", revisionId)
.getResultStream()
.findFirst();
});
}
/**
* Try to save the given {@link SignedMarkRevocationList} into Cloud SQL. If the save fails, the
* error will be logged but no exception will be thrown.
*
* <p>This method is used during the dual-write phase of database migration as Datastore is still
* the authoritative database.
*/
static void trySave(SignedMarkRevocationList signedMarkRevocationList) {
try {
SignedMarkRevocationListDao.save(signedMarkRevocationList);
logger.atInfo().log(
"Inserted %,d signed mark revocations into Cloud SQL",
signedMarkRevocationList.revokes.size());
} catch (Throwable e) {
logger.atSevere().withCause(e).log("Error inserting signed mark revocations into Cloud SQL");
}
}
private static void save(SignedMarkRevocationList signedMarkRevocationList) {
jpaTm().transact(() -> jpaTm().getEntityManager().persist(signedMarkRevocationList));
}
}

View file

@ -62,6 +62,7 @@
<class>google.registry.model.registry.Registry</class>
<class>google.registry.model.reporting.DomainTransactionRecord</class>
<class>google.registry.model.reporting.Spec11ThreatMatch</class>
<class>google.registry.model.smd.SignedMarkRevocationList</class>
<class>google.registry.model.tmch.ClaimsListShard</class>
<class>google.registry.persistence.transaction.TransactionEntity</class>
<class>google.registry.schema.cursor.Cursor</class>

View file

@ -0,0 +1,104 @@
// 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.model.smd;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.ImmutableObjectSubject.assertAboutImmutableObjects;
import com.google.common.collect.ImmutableMap;
import google.registry.persistence.transaction.JpaTestRules;
import google.registry.persistence.transaction.JpaTestRules.JpaIntegrationWithCoverageExtension;
import google.registry.testing.DatastoreEntityExtension;
import google.registry.testing.DualDatabaseTest;
import google.registry.testing.FakeClock;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
@DualDatabaseTest
public class SignedMarkRevocationListDaoTest {
private final FakeClock fakeClock = new FakeClock();
@RegisterExtension
final JpaIntegrationWithCoverageExtension jpa =
new JpaTestRules.Builder().withClock(fakeClock).buildIntegrationWithCoverageExtension();
@RegisterExtension
@Order(value = 1)
final DatastoreEntityExtension datastoreEntityExtension = new DatastoreEntityExtension();
@Test
void testSave_success() {
SignedMarkRevocationList list =
SignedMarkRevocationList.create(
fakeClock.nowUtc(), ImmutableMap.of("mark", fakeClock.nowUtc().minusHours(1)));
SignedMarkRevocationListDao.trySave(list);
SignedMarkRevocationList fromDb = SignedMarkRevocationListDao.getLatestRevision().get();
assertAboutImmutableObjects().that(fromDb).isEqualExceptFields(list);
}
@Test
void trySave_failureIsSwallowed() {
SignedMarkRevocationList list =
SignedMarkRevocationList.create(
fakeClock.nowUtc(), ImmutableMap.of("mark", fakeClock.nowUtc().minusHours(1)));
SignedMarkRevocationListDao.trySave(list);
SignedMarkRevocationList fromDb = SignedMarkRevocationListDao.getLatestRevision().get();
assertAboutImmutableObjects().that(fromDb).isEqualExceptFields(list);
// This should throw an exception, which is swallowed and nothing changed
SignedMarkRevocationListDao.trySave(list);
SignedMarkRevocationList secondFromDb = SignedMarkRevocationListDao.getLatestRevision().get();
assertAboutImmutableObjects().that(secondFromDb).isEqualExceptFields(fromDb);
}
@Test
void testRetrieval_notPresent() {
assertThat(SignedMarkRevocationListDao.getLatestRevision().isPresent()).isFalse();
}
@Test
void testSaveAndRetrieval_emptyList() {
SignedMarkRevocationList list =
SignedMarkRevocationList.create(fakeClock.nowUtc(), ImmutableMap.of());
SignedMarkRevocationListDao.trySave(list);
SignedMarkRevocationList fromDb = SignedMarkRevocationListDao.getLatestRevision().get();
assertAboutImmutableObjects().that(fromDb).isEqualExceptFields(list);
}
@Test
void testSave_multipleVersions() {
SignedMarkRevocationList list =
SignedMarkRevocationList.create(
fakeClock.nowUtc(), ImmutableMap.of("mark", fakeClock.nowUtc().minusHours(1)));
SignedMarkRevocationListDao.trySave(list);
assertThat(
SignedMarkRevocationListDao.getLatestRevision()
.get()
.isSmdRevoked("mark", fakeClock.nowUtc()))
.isTrue();
// Now remove the revocation
SignedMarkRevocationList secondList =
SignedMarkRevocationList.create(fakeClock.nowUtc(), ImmutableMap.of());
SignedMarkRevocationListDao.trySave(secondList);
assertThat(
SignedMarkRevocationListDao.getLatestRevision()
.get()
.isSmdRevoked("mark", fakeClock.nowUtc()))
.isFalse();
}
}

View file

@ -15,6 +15,7 @@
package google.registry.model.smd;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.ImmutableObjectSubject.assertAboutImmutableObjects;
import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.model.smd.SignedMarkRevocationList.SHARD_SIZE;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
@ -72,10 +73,11 @@ public class SignedMarkRevocationListTest {
revokes.put(Integer.toString(i), clock.nowUtc());
}
// Save it with sharding, and make sure that reloading it works.
SignedMarkRevocationList unsharded = SignedMarkRevocationList
.create(clock.nowUtc(), revokes.build())
.save();
assertThat(SignedMarkRevocationList.get()).isEqualTo(unsharded);
SignedMarkRevocationList unsharded =
SignedMarkRevocationList.create(clock.nowUtc(), revokes.build()).save();
assertAboutImmutableObjects()
.that(SignedMarkRevocationList.get())
.isEqualExceptFields(unsharded, "revisionId");
assertThat(ofy().load().type(SignedMarkRevocationList.class).count()).isEqualTo(2);
}
@ -91,7 +93,9 @@ public class SignedMarkRevocationListTest {
SignedMarkRevocationList unsharded = SignedMarkRevocationList
.create(clock.nowUtc(), revokes.build())
.save();
assertThat(SignedMarkRevocationList.get()).isEqualTo(unsharded);
assertAboutImmutableObjects()
.that(SignedMarkRevocationList.get())
.isEqualExceptFields(unsharded, "revisionId");
assertThat(ofy().load().type(SignedMarkRevocationList.class).count()).isEqualTo(4);
}

View file

@ -29,6 +29,7 @@ import google.registry.model.registry.RegistryLockDaoTest;
import google.registry.model.registry.RegistryTest;
import google.registry.model.registry.label.ReservedListSqlDaoTest;
import google.registry.model.reporting.Spec11ThreatMatchTest;
import google.registry.model.smd.SignedMarkRevocationListDaoTest;
import google.registry.model.tmch.ClaimsListDaoTest;
import google.registry.persistence.transaction.JpaEntityCoverageExtension;
import google.registry.persistence.transaction.JpaTestRules.JpaIntegrationWithCoverageExtension;
@ -92,6 +93,7 @@ import org.junit.runner.RunWith;
RegistryTest.class,
ReservedListSqlDaoTest.class,
RegistryLockDaoTest.class,
SignedMarkRevocationListDaoTest.class,
Spec11ThreatMatchTest.class,
// AfterSuiteTest must be the last entry. See class javadoc for details.
AfterSuiteTest.class

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -67,3 +67,4 @@ V66__create_rde_revision.sql
V67__grace_period_history_ids.sql
V68__make_reserved_list_nullable_in_registry.sql
V69__change_primary_key_and_add_history_table_for_delegation_signer.sql
V70__signed_mark_revocation_list.sql

View file

@ -0,0 +1,31 @@
-- 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 "SignedMarkRevocationEntry" (
revision_id int8 not null,
revocation_time timestamptz not null,
smd_id text not null,
primary key (revision_id, smd_id)
);
create table "SignedMarkRevocationList" (
revision_id bigserial not null,
creation_time timestamptz,
primary key (revision_id)
);
alter table if exists "SignedMarkRevocationEntry"
add constraint FK5ivlhvs3121yx2li5tqh54u4
foreign key (revision_id)
references "SignedMarkRevocationList";

View file

@ -617,6 +617,19 @@
primary key (revision_id)
);
create table "SignedMarkRevocationEntry" (
revision_id int8 not null,
revocation_time timestamptz not null,
smd_id text not null,
primary key (revision_id, smd_id)
);
create table "SignedMarkRevocationList" (
revision_id bigserial not null,
creation_time timestamptz,
primary key (revision_id)
);
create table "Spec11ThreatMatch" (
id bigserial not null,
check_date date not null,
@ -779,3 +792,8 @@ create index spec11threatmatch_check_date_idx on "Spec11ThreatMatch" (check_date
add constraint FKgq03rk0bt1hb915dnyvd3vnfc
foreign key (revision_id)
references "ReservedList";
alter table if exists "SignedMarkRevocationEntry"
add constraint FK5ivlhvs3121yx2li5tqh54u4
foreign key (revision_id)
references "SignedMarkRevocationList";

View file

@ -886,6 +886,46 @@ CREATE SEQUENCE public."SafeBrowsingThreat_id_seq"
ALTER SEQUENCE public."SafeBrowsingThreat_id_seq" OWNED BY public."Spec11ThreatMatch".id;
--
-- Name: SignedMarkRevocationEntry; Type: TABLE; Schema: public; Owner: -
--
CREATE TABLE public."SignedMarkRevocationEntry" (
revision_id bigint NOT NULL,
revocation_time timestamp with time zone NOT NULL,
smd_id text NOT NULL
);
--
-- Name: SignedMarkRevocationList; Type: TABLE; Schema: public; Owner: -
--
CREATE TABLE public."SignedMarkRevocationList" (
revision_id bigint NOT NULL,
creation_time timestamp with time zone
);
--
-- Name: SignedMarkRevocationList_revision_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--
CREATE SEQUENCE public."SignedMarkRevocationList_revision_id_seq"
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
--
-- Name: SignedMarkRevocationList_revision_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--
ALTER SEQUENCE public."SignedMarkRevocationList_revision_id_seq" OWNED BY public."SignedMarkRevocationList".revision_id;
--
-- Name: Tld; Type: TABLE; Schema: public; Owner: -
--
@ -996,6 +1036,13 @@ ALTER TABLE ONLY public."RegistryLock" ALTER COLUMN revision_id SET DEFAULT next
ALTER TABLE ONLY public."ReservedList" ALTER COLUMN revision_id SET DEFAULT nextval('public."ReservedList_revision_id_seq"'::regclass);
--
-- Name: SignedMarkRevocationList revision_id; Type: DEFAULT; Schema: public; Owner: -
--
ALTER TABLE ONLY public."SignedMarkRevocationList" ALTER COLUMN revision_id SET DEFAULT nextval('public."SignedMarkRevocationList_revision_id_seq"'::regclass);
--
-- Name: Spec11ThreatMatch id; Type: DEFAULT; Schema: public; Owner: -
--
@ -1234,6 +1281,22 @@ ALTER TABLE ONLY public."Spec11ThreatMatch"
ADD CONSTRAINT "SafeBrowsingThreat_pkey" PRIMARY KEY (id);
--
-- Name: SignedMarkRevocationEntry SignedMarkRevocationEntry_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public."SignedMarkRevocationEntry"
ADD CONSTRAINT "SignedMarkRevocationEntry_pkey" PRIMARY KEY (revision_id, smd_id);
--
-- Name: SignedMarkRevocationList SignedMarkRevocationList_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public."SignedMarkRevocationList"
ADD CONSTRAINT "SignedMarkRevocationList_pkey" PRIMARY KEY (revision_id);
--
-- Name: Tld Tld_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--
@ -1655,6 +1718,14 @@ ALTER TABLE ONLY public."HostHistory"
ADD CONSTRAINT fk3d09knnmxrt6iniwnp8j2ykga FOREIGN KEY (history_registrar_id) REFERENCES public."Registrar"(registrar_id);
--
-- Name: SignedMarkRevocationEntry fk5ivlhvs3121yx2li5tqh54u4; Type: FK CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public."SignedMarkRevocationEntry"
ADD CONSTRAINT fk5ivlhvs3121yx2li5tqh54u4 FOREIGN KEY (revision_id) REFERENCES public."SignedMarkRevocationList"(revision_id);
--
-- Name: ClaimsEntry fk6sc6at5hedffc0nhdcab6ivuq; Type: FK CONSTRAINT; Schema: public; Owner: -
--