mirror of
https://github.com/google/nomulus.git
synced 2025-05-19 10:49:35 +02:00
Add the schema and DAO for Locks in CloudSQL (#462)
* Add the schema and DAO for Locks in CloudSQL * Addresses some comments * Change number on flyway file * Small changes * More small changes * Use checkArgumentNotNull instead of checkNotNull * Address comments * fix javadocs * update persistence
This commit is contained in:
parent
62433c2238
commit
22a879e655
8 changed files with 392 additions and 0 deletions
132
core/src/main/java/google/registry/schema/server/Lock.java
Normal file
132
core/src/main/java/google/registry/schema/server/Lock.java
Normal file
|
@ -0,0 +1,132 @@
|
||||||
|
// 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.schema.server;
|
||||||
|
|
||||||
|
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
|
||||||
|
|
||||||
|
import google.registry.model.ImmutableObject;
|
||||||
|
import google.registry.schema.server.Lock.LockId;
|
||||||
|
import google.registry.util.DateTimeUtils;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.time.ZonedDateTime;
|
||||||
|
import javax.persistence.Column;
|
||||||
|
import javax.persistence.Entity;
|
||||||
|
import javax.persistence.Id;
|
||||||
|
import javax.persistence.IdClass;
|
||||||
|
import javax.persistence.Table;
|
||||||
|
import org.joda.time.DateTime;
|
||||||
|
import org.joda.time.Duration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A lock on some shared resource.
|
||||||
|
*
|
||||||
|
* <p>Locks are either specific to a tld or global to the entire system, in which case a tld of
|
||||||
|
* {@link GLOBAL} is used.
|
||||||
|
*
|
||||||
|
* <p>This uses a compound primary key as defined in {@link LockId}.
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Table
|
||||||
|
@IdClass(LockId.class)
|
||||||
|
public class Lock {
|
||||||
|
|
||||||
|
/** The resource name used to create the lock. */
|
||||||
|
@Column(nullable = false)
|
||||||
|
@Id
|
||||||
|
String resourceName;
|
||||||
|
|
||||||
|
/** The tld used to create the lock. */
|
||||||
|
@Column(nullable = false)
|
||||||
|
@Id
|
||||||
|
String tld;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unique log ID of the request that owns this lock.
|
||||||
|
*
|
||||||
|
* <p>When that request is no longer running (is finished), the lock can be considered implicitly
|
||||||
|
* released.
|
||||||
|
*
|
||||||
|
* <p>See {@link RequestStatusCheckerImpl#getLogId} for details about how it's created in
|
||||||
|
* practice.
|
||||||
|
*/
|
||||||
|
@Column(nullable = false)
|
||||||
|
String requestLogId;
|
||||||
|
|
||||||
|
/** When the lock was acquired. Used for logging. */
|
||||||
|
@Column(nullable = false)
|
||||||
|
ZonedDateTime acquiredTime;
|
||||||
|
|
||||||
|
/** When the lock can be considered implicitly released. */
|
||||||
|
@Column(nullable = false)
|
||||||
|
ZonedDateTime expirationTime;
|
||||||
|
|
||||||
|
/** The scope of a lock that is not specific to a single tld. */
|
||||||
|
static final String GLOBAL = "GLOBAL";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate input and create a new {@link Lock} for the given resource name in the specified tld.
|
||||||
|
*/
|
||||||
|
private Lock(
|
||||||
|
String resourceName,
|
||||||
|
String tld,
|
||||||
|
String requestLogId,
|
||||||
|
DateTime acquiredTime,
|
||||||
|
Duration leaseLength) {
|
||||||
|
this.resourceName = checkArgumentNotNull(resourceName, "The resource name cannot be null");
|
||||||
|
this.tld = checkArgumentNotNull(tld, "The tld cannot be null. For a global lock, use GLOBAL");
|
||||||
|
this.requestLogId =
|
||||||
|
checkArgumentNotNull(requestLogId, "The requestLogId of the lock cannot be null");
|
||||||
|
this.acquiredTime =
|
||||||
|
DateTimeUtils.toZonedDateTime(
|
||||||
|
checkArgumentNotNull(acquiredTime, "The acquired time of the lock cannot be null"));
|
||||||
|
checkArgumentNotNull(leaseLength, "The lease length of the lock cannot be null");
|
||||||
|
this.expirationTime = DateTimeUtils.toZonedDateTime(acquiredTime.plus(leaseLength));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hibernate requires a default constructor.
|
||||||
|
private Lock() {}
|
||||||
|
|
||||||
|
/** Constructs a {@link Lock} object. */
|
||||||
|
public static Lock create(
|
||||||
|
String resourceName,
|
||||||
|
String tld,
|
||||||
|
String requestLogId,
|
||||||
|
DateTime acquiredTime,
|
||||||
|
Duration leaseLength) {
|
||||||
|
checkArgumentNotNull(
|
||||||
|
tld, "The tld cannot be null. To create a global lock, use the createGlobal method");
|
||||||
|
return new Lock(resourceName, tld, requestLogId, acquiredTime, leaseLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Constructs a {@link Lock} object with a {@link GLOBAL} scope. */
|
||||||
|
public static Lock createGlobal(
|
||||||
|
String resourceName, String requestLogId, DateTime acquiredTime, Duration leaseLength) {
|
||||||
|
return new Lock(resourceName, GLOBAL, requestLogId, acquiredTime, leaseLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
static class LockId extends ImmutableObject implements Serializable {
|
||||||
|
|
||||||
|
String resourceName;
|
||||||
|
|
||||||
|
String tld;
|
||||||
|
|
||||||
|
private LockId() {}
|
||||||
|
|
||||||
|
LockId(String resourceName, String tld) {
|
||||||
|
this.resourceName = checkArgumentNotNull(resourceName, "The resource name cannot be null");
|
||||||
|
this.tld = tld;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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.schema.server;
|
||||||
|
|
||||||
|
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
|
||||||
|
import static google.registry.schema.server.Lock.GLOBAL;
|
||||||
|
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
|
||||||
|
|
||||||
|
import google.registry.schema.server.Lock.LockId;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
/** Data access object class for {@link Lock}. */
|
||||||
|
public class LockDao {
|
||||||
|
|
||||||
|
/** Saves the {@link Lock} object to Cloud SQL. */
|
||||||
|
public static void saveNew(Lock lock) {
|
||||||
|
jpaTm()
|
||||||
|
.transact(
|
||||||
|
() -> {
|
||||||
|
jpaTm().getEntityManager().persist(lock);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads and returns a {@link Lock} object with the given resourceName and tld from Cloud SQL if
|
||||||
|
* it exists, else empty.
|
||||||
|
*/
|
||||||
|
public static Optional<Lock> load(String resourceName, String tld) {
|
||||||
|
checkArgumentNotNull(resourceName, "The resource name of the lock to load cannot be null");
|
||||||
|
checkArgumentNotNull(tld, "The tld of the lock to load cannot be null");
|
||||||
|
return Optional.ofNullable(
|
||||||
|
jpaTm()
|
||||||
|
.transact(
|
||||||
|
() -> jpaTm().getEntityManager().find(Lock.class, new LockId(resourceName, tld))));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads a global {@link Lock} object with the given resourceName from Cloud SQL if it exists,
|
||||||
|
* else empty.
|
||||||
|
*/
|
||||||
|
public static Optional<Lock> load(String resourceName) {
|
||||||
|
checkArgumentNotNull(resourceName, "The resource name of the lock to load cannot be null");
|
||||||
|
return Optional.ofNullable(
|
||||||
|
jpaTm()
|
||||||
|
.transact(
|
||||||
|
() ->
|
||||||
|
jpaTm().getEntityManager().find(Lock.class, new LockId(resourceName, GLOBAL))));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes the given {@link Lock} object from Cloud SQL. This method is idempotent and will simply
|
||||||
|
* return if the lock has already been deleted.
|
||||||
|
*/
|
||||||
|
public static void delete(Lock lock) {
|
||||||
|
jpaTm()
|
||||||
|
.transact(
|
||||||
|
() -> {
|
||||||
|
Optional<Lock> loadedLock = load(lock.resourceName, lock.tld);
|
||||||
|
if (loadedLock.isPresent()) {
|
||||||
|
jpaTm().getEntityManager().remove(loadedLock.get());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -25,6 +25,7 @@
|
||||||
<class>google.registry.schema.domain.RegistryLock</class>
|
<class>google.registry.schema.domain.RegistryLock</class>
|
||||||
<class>google.registry.schema.tmch.ClaimsList</class>
|
<class>google.registry.schema.tmch.ClaimsList</class>
|
||||||
<class>google.registry.schema.cursor.Cursor</class>
|
<class>google.registry.schema.cursor.Cursor</class>
|
||||||
|
<class>google.registry.schema.server.Lock</class>
|
||||||
<class>google.registry.schema.tld.PremiumList</class>
|
<class>google.registry.schema.tld.PremiumList</class>
|
||||||
<class>google.registry.schema.tld.PremiumEntry</class>
|
<class>google.registry.schema.tld.PremiumEntry</class>
|
||||||
<class>google.registry.schema.tld.ReservedList</class>
|
<class>google.registry.schema.tld.ReservedList</class>
|
||||||
|
|
|
@ -19,6 +19,7 @@ import google.registry.model.domain.DomainBaseSqlTest;
|
||||||
import google.registry.model.registry.RegistryLockDaoTest;
|
import google.registry.model.registry.RegistryLockDaoTest;
|
||||||
import google.registry.persistence.transaction.JpaEntityCoverage;
|
import google.registry.persistence.transaction.JpaEntityCoverage;
|
||||||
import google.registry.schema.cursor.CursorDaoTest;
|
import google.registry.schema.cursor.CursorDaoTest;
|
||||||
|
import google.registry.schema.server.LockDaoTest;
|
||||||
import google.registry.schema.tld.PremiumListDaoTest;
|
import google.registry.schema.tld.PremiumListDaoTest;
|
||||||
import google.registry.schema.tld.ReservedListDaoTest;
|
import google.registry.schema.tld.ReservedListDaoTest;
|
||||||
import google.registry.schema.tmch.ClaimsListDaoTest;
|
import google.registry.schema.tmch.ClaimsListDaoTest;
|
||||||
|
@ -56,6 +57,7 @@ import org.junit.runners.Suite.SuiteClasses;
|
||||||
CreateReservedListCommandTest.class,
|
CreateReservedListCommandTest.class,
|
||||||
CursorDaoTest.class,
|
CursorDaoTest.class,
|
||||||
DomainLockUtilsTest.class,
|
DomainLockUtilsTest.class,
|
||||||
|
LockDaoTest.class,
|
||||||
LockDomainCommandTest.class,
|
LockDomainCommandTest.class,
|
||||||
DomainBaseSqlTest.class,
|
DomainBaseSqlTest.class,
|
||||||
PremiumListDaoTest.class,
|
PremiumListDaoTest.class,
|
||||||
|
|
|
@ -0,0 +1,129 @@
|
||||||
|
// 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.schema.server;
|
||||||
|
|
||||||
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
import static org.junit.Assert.assertThrows;
|
||||||
|
|
||||||
|
import google.registry.persistence.transaction.JpaTestRules;
|
||||||
|
import google.registry.persistence.transaction.JpaTestRules.JpaIntegrationWithCoverageRule;
|
||||||
|
import google.registry.testing.FakeClock;
|
||||||
|
import java.util.Optional;
|
||||||
|
import javax.persistence.RollbackException;
|
||||||
|
import org.joda.time.Duration;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.junit.runners.JUnit4;
|
||||||
|
|
||||||
|
/** Unit tests for {@link Lock}. */
|
||||||
|
@RunWith(JUnit4.class)
|
||||||
|
public class LockDaoTest {
|
||||||
|
|
||||||
|
private final FakeClock fakeClock = new FakeClock();
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public final JpaIntegrationWithCoverageRule jpaRule =
|
||||||
|
new JpaTestRules.Builder().withClock(fakeClock).buildIntegrationWithCoverageRule();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void save_worksSuccessfully() {
|
||||||
|
Lock lock =
|
||||||
|
Lock.create("testResource", "tld", "testLogId", fakeClock.nowUtc(), Duration.millis(2));
|
||||||
|
LockDao.saveNew(lock);
|
||||||
|
Optional<Lock> returnedLock = LockDao.load("testResource", "tld");
|
||||||
|
assertThat(returnedLock.get().expirationTime).isEqualTo(lock.expirationTime);
|
||||||
|
assertThat(returnedLock.get().requestLogId).isEqualTo(lock.requestLogId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void save_failsWhenLockAlreadyExists() {
|
||||||
|
Lock lock =
|
||||||
|
Lock.create("testResource", "tld", "testLogId", fakeClock.nowUtc(), Duration.millis(2));
|
||||||
|
LockDao.saveNew(lock);
|
||||||
|
Lock lock2 =
|
||||||
|
Lock.create("testResource", "tld", "testLogId2", fakeClock.nowUtc(), Duration.millis(4));
|
||||||
|
RollbackException thrown = assertThrows(RollbackException.class, () -> LockDao.saveNew(lock2));
|
||||||
|
assertThat(thrown.getCause().getCause().getCause().getMessage())
|
||||||
|
.contains("duplicate key value violates unique constraint");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void save_worksSuccesfullyGlobalLock() {
|
||||||
|
Lock lock =
|
||||||
|
Lock.createGlobal("testResource", "testLogId", fakeClock.nowUtc(), Duration.millis(2));
|
||||||
|
LockDao.saveNew(lock);
|
||||||
|
Optional<Lock> returnedLock = LockDao.load("testResource");
|
||||||
|
assertThat(returnedLock.get().expirationTime).isEqualTo(lock.expirationTime);
|
||||||
|
assertThat(returnedLock.get().requestLogId).isEqualTo(lock.requestLogId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void load_worksSuccessfully() {
|
||||||
|
Lock lock =
|
||||||
|
Lock.create("testResource", "tld", "testLogId", fakeClock.nowUtc(), Duration.millis(2));
|
||||||
|
LockDao.saveNew(lock);
|
||||||
|
Optional<Lock> returnedLock = LockDao.load("testResource", "tld");
|
||||||
|
assertThat(returnedLock.get().expirationTime).isEqualTo(lock.expirationTime);
|
||||||
|
assertThat(returnedLock.get().requestLogId).isEqualTo(lock.requestLogId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void load_worksSuccessfullyGlobalLock() {
|
||||||
|
Lock lock =
|
||||||
|
Lock.createGlobal("testResource", "testLogId", fakeClock.nowUtc(), Duration.millis(2));
|
||||||
|
LockDao.saveNew(lock);
|
||||||
|
Optional<Lock> returnedLock = LockDao.load("testResource");
|
||||||
|
assertThat(returnedLock.get().expirationTime).isEqualTo(lock.expirationTime);
|
||||||
|
assertThat(returnedLock.get().requestLogId).isEqualTo(lock.requestLogId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void load_worksSuccesfullyLockDoesNotExist() {
|
||||||
|
Optional<Lock> returnedLock = LockDao.load("testResource", "tld");
|
||||||
|
assertThat(returnedLock.isPresent()).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void delete_worksSuccesfully() {
|
||||||
|
Lock lock =
|
||||||
|
Lock.create("testResource", "tld", "testLogId", fakeClock.nowUtc(), Duration.millis(2));
|
||||||
|
LockDao.saveNew(lock);
|
||||||
|
Optional<Lock> returnedLock = LockDao.load("testResource", "tld");
|
||||||
|
assertThat(returnedLock.get().expirationTime).isEqualTo(lock.expirationTime);
|
||||||
|
LockDao.delete(lock);
|
||||||
|
returnedLock = LockDao.load("testResource", "tld");
|
||||||
|
assertThat(returnedLock.isPresent()).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void delete_worksSuccessfullyGlobalLock() {
|
||||||
|
Lock lock =
|
||||||
|
Lock.createGlobal("testResource", "testLogId", fakeClock.nowUtc(), Duration.millis(2));
|
||||||
|
LockDao.saveNew(lock);
|
||||||
|
Optional<Lock> returnedLock = LockDao.load("testResource");
|
||||||
|
assertThat(returnedLock.get().expirationTime).isEqualTo(lock.expirationTime);
|
||||||
|
LockDao.delete(lock);
|
||||||
|
returnedLock = LockDao.load("testResource");
|
||||||
|
assertThat(returnedLock.isPresent()).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void delete_succeedsLockDoesntExist() {
|
||||||
|
Lock lock =
|
||||||
|
Lock.createGlobal("testResource", "testLogId", fakeClock.nowUtc(), Duration.millis(2));
|
||||||
|
LockDao.delete(lock);
|
||||||
|
}
|
||||||
|
}
|
22
db/src/main/resources/sql/flyway/V18__create_lock.sql
Normal file
22
db/src/main/resources/sql/flyway/V18__create_lock.sql
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
-- 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 "Lock" (
|
||||||
|
resource_name text not null,
|
||||||
|
tld text not null,
|
||||||
|
acquired_time timestamptz not null,
|
||||||
|
expiration_time timestamptz not null,
|
||||||
|
request_log_id text not null,
|
||||||
|
primary key (resource_name, tld)
|
||||||
|
);
|
|
@ -77,6 +77,15 @@
|
||||||
primary key (id)
|
primary key (id)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
create table "Lock" (
|
||||||
|
resource_name text not null,
|
||||||
|
tld text not null,
|
||||||
|
acquired_time timestamptz not null,
|
||||||
|
expiration_time timestamptz not null,
|
||||||
|
request_log_id text not null,
|
||||||
|
primary key (resource_name, tld)
|
||||||
|
);
|
||||||
|
|
||||||
create table "PremiumEntry" (
|
create table "PremiumEntry" (
|
||||||
revision_id int8 not null,
|
revision_id int8 not null,
|
||||||
domain_label text not null,
|
domain_label text not null,
|
||||||
|
|
|
@ -116,6 +116,19 @@ CREATE TABLE public."Domain" (
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: Lock; Type: TABLE; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE public."Lock" (
|
||||||
|
resource_name text NOT NULL,
|
||||||
|
tld text NOT NULL,
|
||||||
|
acquired_time timestamp with time zone NOT NULL,
|
||||||
|
expiration_time timestamp with time zone NOT NULL,
|
||||||
|
request_log_id text NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Name: PremiumEntry; Type: TABLE; Schema: public; Owner: -
|
-- Name: PremiumEntry; Type: TABLE; Schema: public; Owner: -
|
||||||
--
|
--
|
||||||
|
@ -374,6 +387,14 @@ ALTER TABLE ONLY public."Domain"
|
||||||
ADD CONSTRAINT "Domain_pkey" PRIMARY KEY (repo_id);
|
ADD CONSTRAINT "Domain_pkey" PRIMARY KEY (repo_id);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: Lock Lock_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
ALTER TABLE ONLY public."Lock"
|
||||||
|
ADD CONSTRAINT "Lock_pkey" PRIMARY KEY (resource_name, tld);
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Name: PremiumEntry PremiumEntry_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
-- Name: PremiumEntry PremiumEntry_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
||||||
--
|
--
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue