mirror of
https://github.com/google/nomulus.git
synced 2025-06-16 17:34:46 +02:00
Allow disabling UpdateAutoTimestamp updates (#906)
* Allow disabling UpdateAutoTimestamp updates Allow us to disable timestamp updates within a try-with-resources block for a given thread. This functionality will be needed for transaction replays both to and from datastore. As part of this, also upgrade the UpdateAutoTimestampTest to a DualDatabaseTest so we can verify that the functionality works both on Datastore and Cloud SQL.
This commit is contained in:
parent
08738c54c8
commit
090eeaa618
4 changed files with 95 additions and 12 deletions
|
@ -28,6 +28,11 @@ import org.joda.time.DateTime;
|
||||||
*/
|
*/
|
||||||
public class UpdateAutoTimestamp extends ImmutableObject {
|
public class UpdateAutoTimestamp extends ImmutableObject {
|
||||||
|
|
||||||
|
// When set to true, database converters/translators should do tha auto update. When set to
|
||||||
|
// false, auto update should be suspended (this exists to allow us to preserve the original value
|
||||||
|
// during a replay).
|
||||||
|
static ThreadLocal<Boolean> autoUpdateEnabled = ThreadLocal.withInitial(() -> true);
|
||||||
|
|
||||||
DateTime timestamp;
|
DateTime timestamp;
|
||||||
|
|
||||||
/** Returns the timestamp, or {@code START_OF_TIME} if it's null. */
|
/** Returns the timestamp, or {@code START_OF_TIME} if it's null. */
|
||||||
|
@ -40,4 +45,30 @@ public class UpdateAutoTimestamp extends ImmutableObject {
|
||||||
instance.timestamp = timestamp;
|
instance.timestamp = timestamp;
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO(b/175610935): Remove the auto-update disabling code below after migration.
|
||||||
|
|
||||||
|
/** Class to allow us to safely disable auto-update in a try-with-resources block. */
|
||||||
|
public static class DisableAutoUpdateResource implements AutoCloseable {
|
||||||
|
DisableAutoUpdateResource() {
|
||||||
|
autoUpdateEnabled.set(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
autoUpdateEnabled.set(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resturns a resource that disables auto-updates on all {@link UpdateAutoTimestamp}s in the
|
||||||
|
* current thread, suitable for use with in a try-with-resources block.
|
||||||
|
*/
|
||||||
|
public static DisableAutoUpdateResource disableAutoUpdate() {
|
||||||
|
return new DisableAutoUpdateResource();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean autoUpdateEnabled() {
|
||||||
|
return autoUpdateEnabled.get();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,7 +46,10 @@ public class UpdateAutoTimestampTranslatorFactory
|
||||||
/** Save a timestamp, setting it to the current time. */
|
/** Save a timestamp, setting it to the current time. */
|
||||||
@Override
|
@Override
|
||||||
public Date saveValue(UpdateAutoTimestamp pojoValue) {
|
public Date saveValue(UpdateAutoTimestamp pojoValue) {
|
||||||
return tm().getTransactionTime().toDate();
|
return UpdateAutoTimestamp.autoUpdateEnabled()
|
||||||
}};
|
? tm().getTransactionTime().toDate()
|
||||||
|
: pojoValue.getTimestamp().toDate();
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,14 @@ public class UpdateAutoTimestampConverter
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Timestamp convertToDatabaseColumn(UpdateAutoTimestamp entity) {
|
public Timestamp convertToDatabaseColumn(UpdateAutoTimestamp entity) {
|
||||||
return Timestamp.from(DateTimeUtils.toZonedDateTime(jpaTm().getTransactionTime()).toInstant());
|
return Timestamp.from(
|
||||||
|
DateTimeUtils.toZonedDateTime(
|
||||||
|
UpdateAutoTimestamp.autoUpdateEnabled()
|
||||||
|
|| entity == null
|
||||||
|
|| entity.getTimestamp() == null
|
||||||
|
? jpaTm().getTransactionTime()
|
||||||
|
: entity.getTimestamp())
|
||||||
|
.toInstant());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -15,64 +15,106 @@
|
||||||
package google.registry.model;
|
package google.registry.model;
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
import static google.registry.model.ofy.ObjectifyService.ofy;
|
|
||||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||||
import static org.joda.time.DateTimeZone.UTC;
|
import static org.joda.time.DateTimeZone.UTC;
|
||||||
|
|
||||||
|
import com.googlecode.objectify.Key;
|
||||||
import com.googlecode.objectify.annotation.Entity;
|
import com.googlecode.objectify.annotation.Entity;
|
||||||
|
import com.googlecode.objectify.annotation.Ignore;
|
||||||
import google.registry.model.common.CrossTldSingleton;
|
import google.registry.model.common.CrossTldSingleton;
|
||||||
|
import google.registry.persistence.VKey;
|
||||||
import google.registry.schema.replay.EntityTest.EntityForTesting;
|
import google.registry.schema.replay.EntityTest.EntityForTesting;
|
||||||
import google.registry.testing.AppEngineExtension;
|
import google.registry.testing.AppEngineExtension;
|
||||||
|
import google.registry.testing.DualDatabaseTest;
|
||||||
|
import google.registry.testing.FakeClock;
|
||||||
|
import google.registry.testing.TestOfyAndSql;
|
||||||
import org.joda.time.DateTime;
|
import org.joda.time.DateTime;
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||||
|
|
||||||
/** Unit tests for {@link UpdateAutoTimestamp}. */
|
/** Unit tests for {@link UpdateAutoTimestamp}. */
|
||||||
|
@DualDatabaseTest
|
||||||
public class UpdateAutoTimestampTest {
|
public class UpdateAutoTimestampTest {
|
||||||
|
|
||||||
|
FakeClock clock = new FakeClock();
|
||||||
|
|
||||||
@RegisterExtension
|
@RegisterExtension
|
||||||
public final AppEngineExtension appEngine =
|
public final AppEngineExtension appEngine =
|
||||||
AppEngineExtension.builder()
|
AppEngineExtension.builder()
|
||||||
.withDatastoreAndCloudSql()
|
.withDatastoreAndCloudSql()
|
||||||
|
.withJpaUnitTestEntities(UpdateAutoTimestampTestObject.class)
|
||||||
.withOfyTestEntities(UpdateAutoTimestampTestObject.class)
|
.withOfyTestEntities(UpdateAutoTimestampTestObject.class)
|
||||||
|
.withClock(clock)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
/** Timestamped class. */
|
/** Timestamped class. */
|
||||||
@Entity(name = "UatTestEntity")
|
@Entity(name = "UatTestEntity")
|
||||||
|
@javax.persistence.Entity
|
||||||
@EntityForTesting
|
@EntityForTesting
|
||||||
public static class UpdateAutoTimestampTestObject extends CrossTldSingleton {
|
public static class UpdateAutoTimestampTestObject extends CrossTldSingleton {
|
||||||
|
@Ignore @javax.persistence.Id long id = SINGLETON_ID;
|
||||||
UpdateAutoTimestamp updateTime = UpdateAutoTimestamp.create(null);
|
UpdateAutoTimestamp updateTime = UpdateAutoTimestamp.create(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private UpdateAutoTimestampTestObject reload() {
|
private UpdateAutoTimestampTestObject reload() {
|
||||||
return ofy().load().entity(new UpdateAutoTimestampTestObject()).now();
|
return tm().transact(
|
||||||
|
() ->
|
||||||
|
tm().load(
|
||||||
|
VKey.create(
|
||||||
|
UpdateAutoTimestampTestObject.class,
|
||||||
|
1L,
|
||||||
|
Key.create(new UpdateAutoTimestampTestObject()))));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testSaveSetsTime() {
|
void testSaveSetsTime() {
|
||||||
DateTime transactionTime =
|
DateTime transactionTime =
|
||||||
tm().transact(
|
tm().transact(
|
||||||
() -> {
|
() -> {
|
||||||
UpdateAutoTimestampTestObject object = new UpdateAutoTimestampTestObject();
|
UpdateAutoTimestampTestObject object = new UpdateAutoTimestampTestObject();
|
||||||
assertThat(object.updateTime.timestamp).isNull();
|
assertThat(object.updateTime.timestamp).isNull();
|
||||||
ofy().save().entity(object);
|
tm().insert(object);
|
||||||
return tm().getTransactionTime();
|
return tm().getTransactionTime();
|
||||||
});
|
});
|
||||||
ofy().clearSessionCache();
|
tm().clearSessionCache();
|
||||||
assertThat(reload().updateTime.timestamp).isEqualTo(transactionTime);
|
assertThat(reload().updateTime.timestamp).isEqualTo(transactionTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
|
void testDisabledUpdates() throws Exception {
|
||||||
|
DateTime initialTime =
|
||||||
|
tm().transact(
|
||||||
|
() -> {
|
||||||
|
tm().insert(new UpdateAutoTimestampTestObject());
|
||||||
|
return tm().getTransactionTime();
|
||||||
|
});
|
||||||
|
|
||||||
|
UpdateAutoTimestampTestObject object = reload();
|
||||||
|
clock.advanceOneMilli();
|
||||||
|
|
||||||
|
try (UpdateAutoTimestamp.DisableAutoUpdateResource disabler =
|
||||||
|
new UpdateAutoTimestamp.DisableAutoUpdateResource()) {
|
||||||
|
DateTime secondTransactionTime =
|
||||||
|
tm().transact(
|
||||||
|
() -> {
|
||||||
|
tm().put(object);
|
||||||
|
return tm().getTransactionTime();
|
||||||
|
});
|
||||||
|
assertThat(secondTransactionTime).isGreaterThan(initialTime);
|
||||||
|
}
|
||||||
|
assertThat(reload().updateTime.timestamp).isEqualTo(initialTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestOfyAndSql
|
||||||
void testResavingOverwritesOriginalTime() {
|
void testResavingOverwritesOriginalTime() {
|
||||||
DateTime transactionTime =
|
DateTime transactionTime =
|
||||||
tm().transact(
|
tm().transact(
|
||||||
() -> {
|
() -> {
|
||||||
UpdateAutoTimestampTestObject object = new UpdateAutoTimestampTestObject();
|
UpdateAutoTimestampTestObject object = new UpdateAutoTimestampTestObject();
|
||||||
object.updateTime = UpdateAutoTimestamp.create(DateTime.now(UTC).minusDays(1));
|
object.updateTime = UpdateAutoTimestamp.create(DateTime.now(UTC).minusDays(1));
|
||||||
ofy().save().entity(object);
|
tm().insert(object);
|
||||||
return tm().getTransactionTime();
|
return tm().getTransactionTime();
|
||||||
});
|
});
|
||||||
ofy().clearSessionCache();
|
tm().clearSessionCache();
|
||||||
assertThat(reload().updateTime.timestamp).isEqualTo(transactionTime);
|
assertThat(reload().updateTime.timestamp).isEqualTo(transactionTime);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue