Skip to content

Commit da3b3fc

Browse files
committed
add insertion strategy to PersistentValue.foreign
1 parent e1dd7cf commit da3b3fc

7 files changed

Lines changed: 106 additions & 24 deletions

File tree

src/main/java/net/staticstudios/data/DataManager.java

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -413,22 +413,40 @@ private void insertIntoCache(InsertContext context) {
413413
pvHolder.getIdentifier().getColumn()
414414
);
415415

416+
InsertionStrategy insertionStrategy = data.getValue().getInsertionStrategy();
417+
416418
//do not call PersistentValueManager#updateCache since we need to cache both values
417419
//before PersistentCollectionManager#handlePersistentValueCacheUpdated is called, otherwise we get unexpected behavior
418-
cache(data.getValue().getKey(), data.getValue().getDataType(), data.getInitialDataValue(), Instant.now());
420+
boolean updateCache = !cache.containsKey(data.getValue().getKey()) || insertionStrategy == InsertionStrategy.OVERWRITE_EXISTING;
421+
Object oldValue;
422+
423+
try {
424+
oldValue = get(data.getValue().getKey());
425+
if (oldValue == NULL_MARKER) {
426+
oldValue = null;
427+
}
428+
} catch (DataDoesNotExistException e) {
429+
oldValue = null;
430+
}
431+
432+
if (updateCache) {
433+
cache(data.getValue().getKey(), data.getValue().getDataType(), data.getInitialDataValue(), Instant.now());
434+
}
419435
cache(idColumn, UUID.class, context.holder().getId(), Instant.now());
420436

421437

422438
//Alert the collection manager of this change so it can update what it's keeping track of
423-
persistentCollectionManager.handlePersistentValueCacheUpdated(
424-
data.getValue().getSchema(),
425-
data.getValue().getTable(),
426-
data.getValue().getColumn(),
427-
context.holder().getId(),
428-
data.getValue().getIdColumn(),
429-
null,
430-
data.getInitialDataValue()
431-
);
439+
if (updateCache) {
440+
persistentCollectionManager.handlePersistentValueCacheUpdated(
441+
data.getValue().getSchema(),
442+
data.getValue().getTable(),
443+
data.getValue().getColumn(),
444+
context.holder().getId(),
445+
data.getValue().getIdColumn(),
446+
oldValue,
447+
data.getInitialDataValue()
448+
);
449+
}
432450

433451
persistentCollectionManager.handlePersistentValueCacheUpdated(
434452
idColumn.getSchema(),
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package net.staticstudios.data;
2+
3+
public enum InsertionStrategy {
4+
/**
5+
* Overwrite existing data with new data.
6+
*/
7+
OVERWRITE_EXISTING,
8+
/**
9+
* Do not overwrite existing data, only insert if no data exists.
10+
*/
11+
PREFER_EXISTING,
12+
}

src/main/java/net/staticstudios/data/data/value/persistent/PersistentValue.java

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
package net.staticstudios.data.data.value.persistent;
22

3-
import net.staticstudios.data.DataManager;
4-
import net.staticstudios.data.DeletionStrategy;
5-
import net.staticstudios.data.ValueUpdate;
6-
import net.staticstudios.data.ValueUpdateHandler;
3+
import net.staticstudios.data.*;
74
import net.staticstudios.data.data.DataHolder;
85
import net.staticstudios.data.data.UniqueData;
96
import net.staticstudios.data.data.value.Value;
@@ -28,6 +25,7 @@ public class PersistentValue<T> implements Value<T> {
2825
private final DataManager dataManager;
2926
private Supplier<T> defaultValueSupplier;
3027
private DeletionStrategy deletionStrategy;
28+
private InsertionStrategy insertionStrategy;
3129

3230
public PersistentValue(String schema, String table, String column, String idColumn, Class<T> dataType, DataHolder holder, DataManager dataManager) {
3331
if (!holder.getDataManager().isSupportedType(dataType)) {
@@ -56,12 +54,14 @@ public static <T> PersistentValue<T> foreign(UniqueData holder, Class<T> dataTyp
5654
}
5755
PersistentValue<T> pv = new PersistentValue<>(parts[0], parts[1], parts[2], foreignIdColumn, dataType, holder, holder.getDataManager());
5856
pv.deletionStrategy = DeletionStrategy.NO_ACTION;
57+
pv.insertionStrategy = InsertionStrategy.PREFER_EXISTING;
5958
return pv;
6059
}
6160

6261
public static <T> PersistentValue<T> foreign(UniqueData holder, Class<T> dataType, String schema, String table, String column, String foreignIdColumn) {
6362
PersistentValue<T> pv = new PersistentValue<>(schema, table, column, foreignIdColumn, dataType, holder, holder.getDataManager());
6463
pv.deletionStrategy = DeletionStrategy.NO_ACTION;
64+
pv.insertionStrategy = InsertionStrategy.PREFER_EXISTING;
6565
return pv;
6666
}
6767

@@ -159,11 +159,23 @@ public PersistentValue<T> deletionStrategy(DeletionStrategy strategy) {
159159
return this;
160160
}
161161

162+
public PersistentValue<T> insertionStrategy(InsertionStrategy strategy) {
163+
if (holder.getRootHolder().getSchema().equals(schema) && holder.getRootHolder().getTable().equals(table)) {
164+
throw new IllegalArgumentException("Cannot set deletion strategy for a PersistentValue in the same table as it's holder!");
165+
}
166+
this.insertionStrategy = strategy;
167+
return this;
168+
}
169+
162170
@Override
163171
public @NotNull DeletionStrategy getDeletionStrategy() {
164172
return deletionStrategy == null ? DeletionStrategy.NO_ACTION : deletionStrategy;
165173
}
166174

175+
public InsertionStrategy getInsertionStrategy() {
176+
return insertionStrategy == null ? InsertionStrategy.OVERWRITE_EXISTING : insertionStrategy;
177+
}
178+
167179
@Override
168180
public int hashCode() {
169181
return getKey().hashCode();

src/main/java/net/staticstudios/data/impl/PersistentValueManager.java

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import net.staticstudios.data.DataDoesNotExistException;
66
import net.staticstudios.data.DataManager;
77
import net.staticstudios.data.DeleteContext;
8+
import net.staticstudios.data.InsertionStrategy;
89
import net.staticstudios.data.data.Data;
910
import net.staticstudios.data.data.UniqueData;
1011
import net.staticstudios.data.data.value.persistent.InitialPersistentValue;
@@ -199,6 +200,12 @@ public void insertInDatabase(Connection connection, UniqueData holder, List<Init
199200
String schemaTable = idSchemaTable.split("\\.", 2)[1];
200201
Collection<InitialPersistentValue> initialDataValues = initialDataMap.get(idSchemaTable);
201202
initialDataValues.removeIf(i -> i.getValue().getColumn().equals(idColumn));
203+
List<InitialPersistentValue> overwriteExisting = new ArrayList<>();
204+
for (InitialPersistentValue initial : initialDataValues) {
205+
if (initial.getValue().getInsertionStrategy() == InsertionStrategy.OVERWRITE_EXISTING) {
206+
overwriteExisting.add(initial);
207+
}
208+
}
202209

203210
StringBuilder sqlBuilder = new StringBuilder("INSERT INTO ");
204211
sqlBuilder.append(schemaTable);
@@ -216,11 +223,11 @@ public void insertInDatabase(Connection connection, UniqueData holder, List<Init
216223
sqlBuilder.setLength(sqlBuilder.length() - 2);
217224
sqlBuilder.append(")");
218225

219-
if (!initialDataValues.isEmpty()) { //todo: reconsider the conflict thing, conflicts should only happen on FPVs. we should only have this if we have an FPV
226+
if (!overwriteExisting.isEmpty()) {
220227
sqlBuilder.append(" ON CONFLICT (");
221228
sqlBuilder.append(idColumn);
222229
sqlBuilder.append(") DO UPDATE SET ");
223-
for (InitialPersistentValue initial : initialDataValues) {
230+
for (InitialPersistentValue initial : overwriteExisting) {
224231
sqlBuilder.append(initial.getValue().getColumn());
225232
sqlBuilder.append(" = EXCLUDED.");
226233
sqlBuilder.append(initial.getValue().getColumn());
@@ -254,7 +261,6 @@ public void insertInDatabase(Connection connection, UniqueData holder, List<Init
254261

255262
@Blocking
256263
public void updateInDatabase(Connection connection, PersistentValue<?> persistentValue, Object value) throws SQLException {
257-
//todo: when using FPVs, we need to insert if it doesnt exist. handle this case. write a sql function and check if its an fpv to allow this functionality
258264
String schemaTable = persistentValue.getSchema() + "." + persistentValue.getTable();
259265
String idColumn = persistentValue.getIdColumn();
260266
String column = persistentValue.getColumn();

src/test/java/net/staticstudios/data/DeletionTest.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -297,5 +297,4 @@ public void testUnlinkDeletionStrategy() {
297297
//todo: test insertAsync
298298
//todo: test value serializers
299299

300-
//todo: when inserting an FPV, if no explicit initial value was specified (ignore defaults), then if an entry already exists in the db/cache, do not change it. use the existing one.
301300
}

src/test/java/net/staticstudios/data/PersistentValueTest.java

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ public void testSetPersistentValue() {
5959
MockEnvironment environment = getMockEnvironments().getFirst();
6060
DataManager dataManager = environment.dataManager();
6161

62-
DiscordUser user = DiscordUser.createSync(dataManager, "John Doe");
62+
DiscordUser user = DiscordUser.createSync(dataManager, "John Doe", UUID.randomUUID());
6363
assertEquals("John Doe", user.getName());
6464

6565
waitForDataPropagation();
@@ -93,7 +93,7 @@ public void testSetForeignPersistentValue() {
9393
MockEnvironment environment = getMockEnvironments().getFirst();
9494
DataManager dataManager = environment.dataManager();
9595

96-
DiscordUser user = DiscordUser.createSync(dataManager, "John Doe");
96+
DiscordUser user = DiscordUser.createSync(dataManager, "John Doe", UUID.randomUUID());
9797
assertTrue(user.getEnableFriendRequests()); //defaults to true
9898

9999
waitForDataPropagation();
@@ -143,13 +143,46 @@ public void testSetForeignPersistentValue() {
143143
assertFalse(user.getEnableFriendRequests());
144144
}
145145

146+
@RetryingTest(5)
147+
public void testForeignPersistentValuePerferExistingInsertionStrategy() {
148+
UUID id = UUID.randomUUID();
149+
try (Statement statement = getConnection().createStatement()) {
150+
statement.executeUpdate("insert into discord.user_settings (user_id, enable_friend_requests) values ('" + id + "', false)");
151+
} catch (SQLException e) {
152+
throw new RuntimeException(e);
153+
}
154+
MockEnvironment environment = createMockEnvironment();
155+
DataManager dataManager = environment.dataManager();
156+
157+
dataManager.loadAll(DiscordUser.class);
158+
dataManager.loadAll(DiscordUserSettings.class);
159+
160+
assertNull(dataManager.get(DiscordUser.class, id));
161+
162+
DiscordUser user = DiscordUser.createSync(dataManager, "John Doe", id);
163+
assertFalse(user.getEnableFriendRequests());
164+
165+
user.setEnableFriendRequests(true);
166+
assertTrue(user.getEnableFriendRequests());
167+
168+
waitForDataPropagation();
169+
170+
try (Statement statement = getConnection().createStatement()) {
171+
ResultSet resultSet = statement.executeQuery("select enable_friend_requests from discord.user_settings where user_id = '" + id + "'");
172+
resultSet.next();
173+
assertTrue(resultSet.getBoolean("enable_friend_requests"));
174+
} catch (SQLException e) {
175+
throw new RuntimeException(e);
176+
}
177+
}
178+
146179
@RetryingTest(5)
147180
@Disabled("see todo message. the datamanager no longer receives its own pg notifications, so this will never work until we implement the TODO")
148181
public void testSetForeignPersistentValueAndSeePersistentValueUpdate() {
149182
MockEnvironment environment = getMockEnvironments().getFirst();
150183
DataManager dataManager = environment.dataManager();
151184

152-
DiscordUser user = DiscordUser.createSync(dataManager, "John Doe");
185+
DiscordUser user = DiscordUser.createSync(dataManager, "John Doe", UUID.randomUUID());
153186
assertTrue(user.getEnableFriendRequests()); //defaults to true
154187

155188
//todo: the settings should be created as soon as the user is, but this is not the case since they are completely separate entities
@@ -191,7 +224,7 @@ public void testPersistentValueUpdateHandlers() {
191224
MockEnvironment environment = getMockEnvironments().getFirst();
192225
DataManager dataManager = environment.dataManager();
193226

194-
DiscordUser user = DiscordUser.createSync(dataManager, "John Doe");
227+
DiscordUser user = DiscordUser.createSync(dataManager, "John Doe", UUID.randomUUID());
195228
assertEquals(0, user.getNameUpdatesCalled());
196229
assertEquals(0, user.getEnableFriendRequestsUpdatesCalled());
197230

src/test/java/net/staticstudios/data/mock/persistentvalue/DiscordUser.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package net.staticstudios.data.mock.persistentvalue;
22

33
import net.staticstudios.data.DataManager;
4+
import net.staticstudios.data.InsertionStrategy;
45
import net.staticstudios.data.data.UniqueData;
56
import net.staticstudios.data.data.value.persistent.PersistentValue;
67

@@ -20,6 +21,7 @@ public class DiscordUser extends UniqueData {
2021
});
2122
private final PersistentValue<Boolean> enableFriendRequests = PersistentValue.foreign(this, Boolean.class, "discord", "user_settings", "enable_friend_requests", "user_id")
2223
.withDefault(true)
24+
.insertionStrategy(InsertionStrategy.PREFER_EXISTING)
2325
.onUpdate(update -> {
2426
if (update.oldValue() == null) {
2527
return;
@@ -31,8 +33,8 @@ private DiscordUser(DataManager dataManager, UUID id) {
3133
super(dataManager, "discord", "users", id);
3234
}
3335

34-
public static DiscordUser createSync(DataManager dataManager, String name) {
35-
DiscordUser user = new DiscordUser(dataManager, UUID.randomUUID());
36+
public static DiscordUser createSync(DataManager dataManager, String name, UUID id) {
37+
DiscordUser user = new DiscordUser(dataManager, id);
3638
dataManager.insert(user, user.name.initial(name));
3739

3840
return user;

0 commit comments

Comments
 (0)