Add schema and DAO for cursors in cloudsql (#370)

* Add schema for Cursor

* Add CursorDao and CursorDaoTest

* Fix comment on getTld

* Change tld column to scope

* Fix cursorTime to be converted to DateTime internally and other small fixes

* Add a CursorType enum and a createGlobal constructor for Cursor

* Rename flyway file

* Use cursorType from common/Cursor.java and add null checks
This commit is contained in:
sarahcaseybot 2019-12-09 17:47:06 -05:00 committed by GitHub
parent bba5aff4b6
commit 2478a4a93b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 370 additions and 0 deletions

View file

@ -0,0 +1,116 @@
// 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.cursor;
import static com.google.appengine.api.search.checkers.Preconditions.checkNotNull;
import google.registry.model.ImmutableObject;
import google.registry.model.UpdateAutoTimestamp;
import google.registry.model.common.Cursor.CursorType;
import google.registry.schema.cursor.Cursor.CursorId;
import google.registry.util.DateTimeUtils;
import java.io.Serializable;
import java.time.ZonedDateTime;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.Table;
import org.joda.time.DateTime;
/**
* Shared entity for date cursors. This uses a compound primary key as defined in {@link CursorId}.
*/
@Entity
@Table
@IdClass(CursorId.class)
public class Cursor {
@Enumerated(EnumType.STRING)
@Column(nullable = false)
@Id
private CursorType type;
@Column @Id private String scope;
@Column(nullable = false)
private ZonedDateTime cursorTime;
@Column(nullable = false)
private UpdateAutoTimestamp lastUpdateTime = UpdateAutoTimestamp.create(null);
/** The scope of a global cursor. A global cursor is a cursor that is not specific to one tld. */
public static final String GLOBAL = "GLOBAL";
private Cursor(CursorType type, String scope, DateTime cursorTime) {
this.type = type;
this.scope = scope;
this.cursorTime = DateTimeUtils.toZonedDateTime(cursorTime);
}
// Hibernate requires a default constructor.
private Cursor() {}
/** Constructs a {@link Cursor} object. */
public static Cursor create(CursorType type, String scope, DateTime cursorTime) {
checkNotNull(
scope, "Scope cannot be null. To create a global cursor, use the createGlobal method");
return new Cursor(type, scope, cursorTime);
}
/** Constructs a {@link Cursor} object with a {@link GLOBAL} scope. */
public static Cursor createGlobal(CursorType type, DateTime cursorTime) {
return new Cursor(type, GLOBAL, cursorTime);
}
/** Returns the type of the cursor. */
public CursorType getType() {
return type;
}
/**
* Returns the scope of the cursor. The scope will typically be the tld the cursor is referring
* to. If the cursor is a global cursor, the scope will be {@link GLOBAL}.
*/
public String getScope() {
return scope;
}
/** Returns the time the cursor is set to. */
public DateTime getCursorTime() {
return DateTimeUtils.toJodaDateTime(cursorTime);
}
/** Returns the last time the cursor was updated. */
public DateTime getLastUpdateTime() {
return lastUpdateTime.getTimestamp();
}
static class CursorId extends ImmutableObject implements Serializable {
public CursorType type;
public String scope;
private CursorId() {}
public CursorId(CursorType type, String scope) {
this.type = type;
this.scope = scope;
}
}
}

View file

@ -0,0 +1,70 @@
// 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.cursor;
import static com.google.appengine.api.search.checkers.Preconditions.checkNotNull;
import static google.registry.model.transaction.TransactionManagerFactory.jpaTm;
import google.registry.model.common.Cursor.CursorType;
import google.registry.schema.cursor.Cursor.CursorId;
import java.util.List;
/** Data access object class for {@link Cursor}. */
public class CursorDao {
public static void save(Cursor cursor) {
jpaTm()
.transact(
() -> {
jpaTm().getEntityManager().merge(cursor);
});
}
public static Cursor load(CursorType type, String scope) {
checkNotNull(scope, "The scope of the cursor to load cannot be null");
checkNotNull(type, "The type of the cursor to load must be specified");
return jpaTm()
.transact(() -> jpaTm().getEntityManager().find(Cursor.class, new CursorId(type, scope)));
}
/** If no scope is given, use {@link Cursor.GLOBAL} as the scope. */
public static Cursor load(CursorType type) {
checkNotNull(type, "The type of the cursor to load must be specified");
return load(type, Cursor.GLOBAL);
}
public static List<Cursor> loadAll() {
return jpaTm()
.transact(
() ->
jpaTm()
.getEntityManager()
.createQuery("SELECT cursor FROM Cursor cursor", Cursor.class)
.getResultList());
}
public static List<Cursor> loadByType(CursorType type) {
checkNotNull(type, "The type of the cursors to load must be specified");
return jpaTm()
.transact(
() ->
jpaTm()
.getEntityManager()
.createQuery(
"SELECT cursor FROM Cursor cursor WHERE cursor.type = :type", Cursor.class)
.setParameter("type", type)
.getResultList());
}
}

View file

@ -22,6 +22,7 @@
<class>google.registry.model.domain.DomainBase</class> <class>google.registry.model.domain.DomainBase</class>
<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.model.transfer.BaseTransferObject</class> <class>google.registry.model.transfer.BaseTransferObject</class>
<class>google.registry.schema.tld.PremiumList</class> <class>google.registry.schema.tld.PremiumList</class>
<class>google.registry.schema.tld.ReservedList</class> <class>google.registry.schema.tld.ReservedList</class>

View file

@ -0,0 +1,132 @@
// 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.cursor;
import static com.google.common.truth.Truth.assertThat;
import google.registry.model.common.Cursor.CursorType;
import google.registry.model.transaction.JpaTransactionManagerRule;
import google.registry.testing.FakeClock;
import java.util.List;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/** Unit tests for {@link Cursor}. */
@RunWith(JUnit4.class)
public class CursorDaoTest {
private FakeClock fakeClock = new FakeClock();
@Rule
public final JpaTransactionManagerRule jpaTmRule =
new JpaTransactionManagerRule.Builder().build();
@Test
public void save_worksSuccessfullyOnNewCursor() {
Cursor cursor = Cursor.create(CursorType.BRDA, "tld", fakeClock.nowUtc());
CursorDao.save(cursor);
Cursor returnedCursor = CursorDao.load(CursorType.BRDA, "tld");
assertThat(returnedCursor.getCursorTime()).isEqualTo(cursor.getCursorTime());
}
@Test
public void save_worksSuccessfullyOnExistingCursor() {
Cursor cursor = Cursor.create(CursorType.RDE_REPORT, "tld", fakeClock.nowUtc());
CursorDao.save(cursor);
Cursor cursor2 = Cursor.create(CursorType.RDE_REPORT, "tld", fakeClock.nowUtc().plusDays(3));
CursorDao.save(cursor2);
Cursor returnedCursor = CursorDao.load(CursorType.RDE_REPORT, "tld");
assertThat(returnedCursor.getCursorTime()).isEqualTo(cursor2.getCursorTime());
}
@Test
public void save_worksSuccessfullyOnNewGlobalCursor() {
Cursor cursor = Cursor.createGlobal(CursorType.RECURRING_BILLING, fakeClock.nowUtc());
CursorDao.save(cursor);
Cursor returnedCursor = CursorDao.load(CursorType.RECURRING_BILLING);
assertThat(returnedCursor.getCursorTime()).isEqualTo(cursor.getCursorTime());
}
@Test
public void save_worksSuccessfullyOnExistingGlobalCursor() {
Cursor cursor = Cursor.createGlobal(CursorType.RECURRING_BILLING, fakeClock.nowUtc());
CursorDao.save(cursor);
Cursor cursor2 =
Cursor.createGlobal(CursorType.RECURRING_BILLING, fakeClock.nowUtc().plusDays(3));
CursorDao.save(cursor2);
Cursor returnedCursor = CursorDao.load(CursorType.RECURRING_BILLING);
assertThat(returnedCursor.getCursorTime()).isEqualTo(cursor2.getCursorTime());
}
@Test
public void load_worksSuccessfully() {
Cursor cursor = Cursor.createGlobal(CursorType.RECURRING_BILLING, fakeClock.nowUtc());
Cursor cursor2 = Cursor.create(CursorType.RDE_REPORT, "tld", fakeClock.nowUtc());
Cursor cursor3 = Cursor.create(CursorType.RDE_REPORT, "foo", fakeClock.nowUtc());
Cursor cursor4 = Cursor.create(CursorType.BRDA, "foo", fakeClock.nowUtc());
CursorDao.save(cursor);
CursorDao.save(cursor2);
CursorDao.save(cursor3);
CursorDao.save(cursor4);
Cursor returnedCursor = CursorDao.load(CursorType.RDE_REPORT, "tld");
assertThat(returnedCursor.getCursorTime()).isEqualTo(cursor2.getCursorTime());
returnedCursor = CursorDao.load(CursorType.BRDA, "foo");
assertThat(returnedCursor.getCursorTime()).isEqualTo(cursor4.getCursorTime());
returnedCursor = CursorDao.load(CursorType.RECURRING_BILLING);
assertThat(returnedCursor.getCursorTime()).isEqualTo(cursor.getCursorTime());
}
@Test
public void loadAll_worksSuccessfully() {
Cursor cursor = Cursor.createGlobal(CursorType.RECURRING_BILLING, fakeClock.nowUtc());
Cursor cursor2 = Cursor.create(CursorType.RDE_REPORT, "tld", fakeClock.nowUtc());
Cursor cursor3 = Cursor.create(CursorType.RDE_REPORT, "foo", fakeClock.nowUtc());
Cursor cursor4 = Cursor.create(CursorType.BRDA, "foo", fakeClock.nowUtc());
CursorDao.save(cursor);
CursorDao.save(cursor2);
CursorDao.save(cursor3);
CursorDao.save(cursor4);
List<Cursor> returnedCursors = CursorDao.loadAll();
assertThat(returnedCursors.size()).isEqualTo(4);
}
@Test
public void loadAll_worksSuccessfullyEmptyTable() {
List<Cursor> returnedCursors = CursorDao.loadAll();
assertThat(returnedCursors.size()).isEqualTo(0);
}
@Test
public void loadByType_worksSuccessfully() {
Cursor cursor = Cursor.createGlobal(CursorType.RECURRING_BILLING, fakeClock.nowUtc());
Cursor cursor2 = Cursor.create(CursorType.RDE_REPORT, "tld", fakeClock.nowUtc());
Cursor cursor3 = Cursor.create(CursorType.RDE_REPORT, "foo", fakeClock.nowUtc());
Cursor cursor4 = Cursor.create(CursorType.BRDA, "foo", fakeClock.nowUtc());
CursorDao.save(cursor);
CursorDao.save(cursor2);
CursorDao.save(cursor3);
CursorDao.save(cursor4);
List<Cursor> returnedCursors = CursorDao.loadByType(CursorType.RDE_REPORT);
assertThat(returnedCursors.size()).isEqualTo(2);
}
@Test
public void loadByType_worksSuccessfullyNoneOfType() {
List<Cursor> returnedCursors = CursorDao.loadByType(CursorType.RDE_REPORT);
assertThat(returnedCursors.size()).isEqualTo(0);
}
}

View file

@ -25,6 +25,7 @@ import google.registry.persistence.DateTimeConverterTest;
import google.registry.persistence.JodaMoneyConverterTest; import google.registry.persistence.JodaMoneyConverterTest;
import google.registry.persistence.UpdateAutoTimestampConverterTest; import google.registry.persistence.UpdateAutoTimestampConverterTest;
import google.registry.persistence.ZonedDateTimeConverterTest; import google.registry.persistence.ZonedDateTimeConverterTest;
import google.registry.schema.cursor.CursorDaoTest;
import google.registry.schema.tld.PremiumListDaoTest; import google.registry.schema.tld.PremiumListDaoTest;
import google.registry.ui.server.registrar.RegistryLockGetActionTest; import google.registry.ui.server.registrar.RegistryLockGetActionTest;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@ -47,6 +48,7 @@ import org.junit.runners.Suite.SuiteClasses;
ClaimsListDaoTest.class, ClaimsListDaoTest.class,
CreateAutoTimestampConverterTest.class, CreateAutoTimestampConverterTest.class,
CurrencyUnitConverterTest.class, CurrencyUnitConverterTest.class,
CursorDaoTest.class,
DateTimeConverterTest.class, DateTimeConverterTest.class,
JodaMoneyConverterTest.class, JodaMoneyConverterTest.class,
JpaTransactionManagerImplTest.class, JpaTransactionManagerImplTest.class,

View file

@ -0,0 +1,21 @@
-- 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 "Cursor" (
scope text not null,
type text not null,
cursor_time timestamptz not null,
last_update_time timestamptz not null,
primary key (scope, type)
);

View file

@ -26,6 +26,14 @@
primary key (revision_id) primary key (revision_id)
); );
create table "Cursor" (
scope text not null,
type text not null,
cursor_time timestamptz not null,
last_update_time timestamptz not null,
primary key (scope, type)
);
create table "DelegationSignerData" ( create table "DelegationSignerData" (
key_tag int4 not null, key_tag int4 not null,
algorithm int4 not null, algorithm int4 not null,

View file

@ -61,6 +61,18 @@ CREATE SEQUENCE public."ClaimsList_revision_id_seq"
ALTER SEQUENCE public."ClaimsList_revision_id_seq" OWNED BY public."ClaimsList".revision_id; ALTER SEQUENCE public."ClaimsList_revision_id_seq" OWNED BY public."ClaimsList".revision_id;
--
-- Name: Cursor; Type: TABLE; Schema: public; Owner: -
--
CREATE TABLE public."Cursor" (
scope text NOT NULL,
type text NOT NULL,
cursor_time timestamp with time zone NOT NULL,
last_update_time timestamp with time zone NOT NULL
);
-- --
-- Name: PremiumEntry; Type: TABLE; Schema: public; Owner: - -- Name: PremiumEntry; Type: TABLE; Schema: public; Owner: -
-- --
@ -228,6 +240,14 @@ ALTER TABLE ONLY public."ClaimsList"
ADD CONSTRAINT "ClaimsList_pkey" PRIMARY KEY (revision_id); ADD CONSTRAINT "ClaimsList_pkey" PRIMARY KEY (revision_id);
--
-- Name: Cursor Cursor_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public."Cursor"
ADD CONSTRAINT "Cursor_pkey" PRIMARY KEY (scope, type);
-- --
-- Name: PremiumEntry PremiumEntry_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- Name: PremiumEntry PremiumEntry_pkey; Type: CONSTRAINT; Schema: public; Owner: -
-- --