From 60683db3f9929f77e02e02f6aa159e8c9cd5c46a Mon Sep 17 00:00:00 2001 From: Patrick Doyle Date: Sun, 13 Nov 2022 16:33:04 -0500 Subject: [PATCH 1/7] Refactor: stop using the old term 'tenant' --- .../main/java/io/vena/bosk/drivers/mongo/MongoDriver.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bosk-mongo/src/main/java/io/vena/bosk/drivers/mongo/MongoDriver.java b/bosk-mongo/src/main/java/io/vena/bosk/drivers/mongo/MongoDriver.java index 12373540..a7cb8304 100644 --- a/bosk-mongo/src/main/java/io/vena/bosk/drivers/mongo/MongoDriver.java +++ b/bosk-mongo/src/main/java/io/vena/bosk/drivers/mongo/MongoDriver.java @@ -115,7 +115,7 @@ public R initialRoot(Type rootType) throws InvalidTypeException, IOException, In } R root = receiver.initialRoot(rootType); - ensureTenantDocumentExists(formatter.object2bsonValue(root, rootType)); + ensureDocumentExists(formatter.object2bsonValue(root, rootType)); return root; } @@ -269,9 +269,9 @@ private BsonDocument deletionDoc(Reference target) { return new BsonDocument("$unset", new BsonDocument(key, new BsonNull())); // Value is ignored } - private void ensureTenantDocumentExists(BsonValue initialState) { + private void ensureDocumentExists(BsonValue initialState) { BsonDocument filter = documentFilter(); - BsonDocument update = initialTenantUpsert(initialState); + BsonDocument update = initialUpsert(initialState); UpdateOptions options = new UpdateOptions(); options.upsert(true); LOGGER.debug("** Initial tenant upsert for {}", documentID); @@ -294,7 +294,7 @@ private void ensureTenantDocumentExists(BsonValue initialState) { LOGGER.debug("| Result: {}", result); } - BsonDocument initialTenantUpsert(BsonValue initialState) { + BsonDocument initialUpsert(BsonValue initialState) { BsonDocument fieldValues = new BsonDocument("_id", documentID); fieldValues.put(state.name(), initialState); fieldValues.put(echo.name(), new BsonString(uniqueEchoToken())); From 0b73ab506a2a9288d6a0609e1bfffa154f986e00 Mon Sep 17 00:00:00 2001 From: Patrick Doyle Date: Sun, 13 Nov 2022 16:52:25 -0500 Subject: [PATCH 2/7] Comment on retrying updates --- .../src/main/java/io/vena/bosk/drivers/mongo/MongoDriver.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bosk-mongo/src/main/java/io/vena/bosk/drivers/mongo/MongoDriver.java b/bosk-mongo/src/main/java/io/vena/bosk/drivers/mongo/MongoDriver.java index a7cb8304..eecdfab5 100644 --- a/bosk-mongo/src/main/java/io/vena/bosk/drivers/mongo/MongoDriver.java +++ b/bosk-mongo/src/main/java/io/vena/bosk/drivers/mongo/MongoDriver.java @@ -285,6 +285,9 @@ private void ensureDocumentExists(BsonValue initialState) { if (DUPLICATE_KEY == ErrorCategory.fromErrorCode(e.getCode())) { // This can happen in MongoDB 4.0 if two upserts occur in parallel. // https://docs.mongodb.com/v4.0/reference/method/db.collection.update/ + // As of MongoDB 4.2, this is no longer required. Since 4.0 is not longer + // supported, we could presumably delete this code. + // https://www.mongodb.com/docs/manual/core/retryable-writes/#std-label-retryable-update-upsert LOGGER.debug("| Retrying: {}", e.getMessage()); result = collection.updateOne(filter, update, options); } else { From 5745ebb26d18f5cecd49ba5b0336e1ed4aaf500e Mon Sep 17 00:00:00 2001 From: Patrick Doyle Date: Sun, 13 Nov 2022 16:52:42 -0500 Subject: [PATCH 3/7] Refactor: initialDocument method --- .../main/java/io/vena/bosk/drivers/mongo/MongoDriver.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bosk-mongo/src/main/java/io/vena/bosk/drivers/mongo/MongoDriver.java b/bosk-mongo/src/main/java/io/vena/bosk/drivers/mongo/MongoDriver.java index eecdfab5..6f934669 100644 --- a/bosk-mongo/src/main/java/io/vena/bosk/drivers/mongo/MongoDriver.java +++ b/bosk-mongo/src/main/java/io/vena/bosk/drivers/mongo/MongoDriver.java @@ -270,8 +270,8 @@ private BsonDocument deletionDoc(Reference target) { } private void ensureDocumentExists(BsonValue initialState) { + BsonDocument update = new BsonDocument("$setOnInsert", initialDocument(initialState)); BsonDocument filter = documentFilter(); - BsonDocument update = initialUpsert(initialState); UpdateOptions options = new UpdateOptions(); options.upsert(true); LOGGER.debug("** Initial tenant upsert for {}", documentID); @@ -297,11 +297,11 @@ private void ensureDocumentExists(BsonValue initialState) { LOGGER.debug("| Result: {}", result); } - BsonDocument initialUpsert(BsonValue initialState) { + private BsonDocument initialDocument(BsonValue initialState) { BsonDocument fieldValues = new BsonDocument("_id", documentID); fieldValues.put(state.name(), initialState); fieldValues.put(echo.name(), new BsonString(uniqueEchoToken())); - return new BsonDocument("$setOnInsert", fieldValues); + return fieldValues; } /** From bf4398031860043676e1617b134d4f57dcfdab93 Mon Sep 17 00:00:00 2001 From: Patrick Doyle Date: Sun, 13 Nov 2022 17:25:41 -0500 Subject: [PATCH 4/7] Refactor: updateCommand parameter --- .../main/java/io/vena/bosk/drivers/mongo/MongoDriver.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bosk-mongo/src/main/java/io/vena/bosk/drivers/mongo/MongoDriver.java b/bosk-mongo/src/main/java/io/vena/bosk/drivers/mongo/MongoDriver.java index 6f934669..4d54985c 100644 --- a/bosk-mongo/src/main/java/io/vena/bosk/drivers/mongo/MongoDriver.java +++ b/bosk-mongo/src/main/java/io/vena/bosk/drivers/mongo/MongoDriver.java @@ -115,7 +115,7 @@ public R initialRoot(Type rootType) throws InvalidTypeException, IOException, In } R root = receiver.initialRoot(rootType); - ensureDocumentExists(formatter.object2bsonValue(root, rootType)); + ensureDocumentExists(formatter.object2bsonValue(root, rootType), "$setOnInsert"); return root; } @@ -269,8 +269,8 @@ private BsonDocument deletionDoc(Reference target) { return new BsonDocument("$unset", new BsonDocument(key, new BsonNull())); // Value is ignored } - private void ensureDocumentExists(BsonValue initialState) { - BsonDocument update = new BsonDocument("$setOnInsert", initialDocument(initialState)); + private void ensureDocumentExists(BsonValue initialState, String updateCommand) { + BsonDocument update = new BsonDocument(updateCommand, initialDocument(initialState)); BsonDocument filter = documentFilter(); UpdateOptions options = new UpdateOptions(); options.upsert(true); From 9835acd6c3c3c9459661513335aacb5f523f1c21 Mon Sep 17 00:00:00 2001 From: Patrick Doyle Date: Sun, 13 Nov 2022 16:33:29 -0500 Subject: [PATCH 5/7] Test that refurbish adds path metadata --- .../io/vena/bosk/drivers/mongo/Formatter.java | 1 + .../drivers/mongo/MongoDriverSpecialTest.java | 56 +++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/bosk-mongo/src/main/java/io/vena/bosk/drivers/mongo/Formatter.java b/bosk-mongo/src/main/java/io/vena/bosk/drivers/mongo/Formatter.java index e54d7367..fc01e4dd 100644 --- a/bosk-mongo/src/main/java/io/vena/bosk/drivers/mongo/Formatter.java +++ b/bosk-mongo/src/main/java/io/vena/bosk/drivers/mongo/Formatter.java @@ -55,6 +55,7 @@ final class Formatter { * No field name should be a prefix of any other. */ enum DocumentFields { + path, state, echo, } diff --git a/bosk-mongo/src/test/java/io/vena/bosk/drivers/mongo/MongoDriverSpecialTest.java b/bosk-mongo/src/test/java/io/vena/bosk/drivers/mongo/MongoDriverSpecialTest.java index 460d4a4b..8f7e6ee4 100644 --- a/bosk-mongo/src/test/java/io/vena/bosk/drivers/mongo/MongoDriverSpecialTest.java +++ b/bosk-mongo/src/test/java/io/vena/bosk/drivers/mongo/MongoDriverSpecialTest.java @@ -1,6 +1,8 @@ package io.vena.bosk.drivers.mongo; import com.mongodb.MongoException; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoCursor; import io.vena.bosk.Bosk; import io.vena.bosk.BoskDriver; import io.vena.bosk.Catalog; @@ -26,16 +28,22 @@ import java.util.concurrent.LinkedBlockingDeque; import lombok.Value; import lombok.experimental.Accessors; +import org.bson.BsonDocument; +import org.bson.BsonNull; +import org.bson.BsonString; +import org.bson.Document; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import static io.vena.bosk.ListingEntry.LISTING_ENTRY; +import static io.vena.bosk.drivers.mongo.Formatter.DocumentFields.path; import static java.lang.Long.max; import static java.lang.System.currentTimeMillis; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; /** @@ -380,6 +388,54 @@ void refurbish_createsField() throws IOException, InterruptedException { assertEquals(Optional.of(TestValues.blank()), after); // Now it's there } + @Test + @UsesMongoService + void refurbish_fixesMetadata() throws IOException, InterruptedException { + // Set up the database so it looks basically right + Bosk initialBosk = new Bosk( + "Initial", + TestEntity.class, + this::initialRoot, + createDriverFactory() + ); + + // (Close this so it doesn't crash when we delete the "path" field) + ((MongoDriver)initialBosk.driver()).close(); + + // Remove the `path` metadata field + MongoCollection collection = mongoService.client() + .getDatabase(driverSettings.database()) + .getCollection(driverSettings.collection()); + BsonDocument filterDoc = new BsonDocument("_id", new BsonString("boskDocument")); + BsonDocument deletionDoc = new BsonDocument("$unset", new BsonDocument(path.name(), new BsonNull())); // Value is ignored + collection.updateOne(filterDoc, deletionDoc); + + // Make the bosk we want to test + Bosk bosk = new Bosk( + "bosk", + TestEntity.class, + this::initialRoot, + createDriverFactory() + ); + + // Verify that the path field is indeed missing + bosk.driver().flush(); + try (MongoCursor cursor = collection.find(filterDoc).cursor()) { + Document doc = cursor.next(); + assertNull(doc.get(path.name())); + } + + // Refurbish + ((MongoDriver)bosk.driver()).refurbish(); + + // Verify that it's there now + try (MongoCursor cursor = collection.find(filterDoc).cursor()) { + Document doc = cursor.next(); + assertEquals("/", doc.get(path.name())); + } + + } + private DriverFactory createDriverFactory() { return (bosk, downstream) -> { MongoDriver driver = new MongoDriver( From a4669941a00604ff6016cf3d0fd519bc656ab000 Mon Sep 17 00:00:00 2001 From: Patrick Doyle Date: Sun, 13 Nov 2022 17:40:01 -0500 Subject: [PATCH 6/7] Add MongoDriver path metadata field --- .../src/main/java/io/vena/bosk/drivers/mongo/MongoDriver.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bosk-mongo/src/main/java/io/vena/bosk/drivers/mongo/MongoDriver.java b/bosk-mongo/src/main/java/io/vena/bosk/drivers/mongo/MongoDriver.java index 4d54985c..bc832fd8 100644 --- a/bosk-mongo/src/main/java/io/vena/bosk/drivers/mongo/MongoDriver.java +++ b/bosk-mongo/src/main/java/io/vena/bosk/drivers/mongo/MongoDriver.java @@ -41,6 +41,7 @@ import static com.mongodb.ErrorCategory.DUPLICATE_KEY; import static io.vena.bosk.drivers.mongo.Formatter.DocumentFields.echo; +import static io.vena.bosk.drivers.mongo.Formatter.DocumentFields.path; import static io.vena.bosk.drivers.mongo.Formatter.DocumentFields.state; import static io.vena.bosk.drivers.mongo.Formatter.dottedFieldNameOf; import static io.vena.bosk.drivers.mongo.Formatter.enclosingReference; @@ -299,6 +300,7 @@ private void ensureDocumentExists(BsonValue initialState, String updateCommand) private BsonDocument initialDocument(BsonValue initialState) { BsonDocument fieldValues = new BsonDocument("_id", documentID); + fieldValues.put(path.name(), new BsonString("/")); fieldValues.put(state.name(), initialState); fieldValues.put(echo.name(), new BsonString(uniqueEchoToken())); return fieldValues; From 8091685ea423170d6a01173b6cbd61a72d856aef Mon Sep 17 00:00:00 2001 From: Patrick Doyle Date: Sun, 13 Nov 2022 17:30:14 -0500 Subject: [PATCH 7/7] Refurbish metadata fields --- .../java/io/vena/bosk/drivers/mongo/MongoDriver.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/bosk-mongo/src/main/java/io/vena/bosk/drivers/mongo/MongoDriver.java b/bosk-mongo/src/main/java/io/vena/bosk/drivers/mongo/MongoDriver.java index bc832fd8..2adf2802 100644 --- a/bosk-mongo/src/main/java/io/vena/bosk/drivers/mongo/MongoDriver.java +++ b/bosk-mongo/src/main/java/io/vena/bosk/drivers/mongo/MongoDriver.java @@ -220,8 +220,17 @@ public void refurbish() { return; } + // Round trip via state tree nodes R root = formatter.document2object(newState, rootRef); - doUpdate(replacementDoc(rootRef, root), documentFilter()); + BsonValue initialState = formatter.object2bsonValue(root, rootRef.targetType()); + + // Start with a blank document so subsequent changes become update events instead of inserts + // TODO: should we do this for initialization too? We want those to be updates as well, right? + collection.replaceOne(documentFilter(), new Document()); + + // Set all the same fields we set on initialization + ensureDocumentExists(initialState, "$set"); + session.commitTransaction(); } finally { if (session.hasActiveTransaction()) {