-
Notifications
You must be signed in to change notification settings - Fork 31
STITCH-1967 Add full CRUD within .sync() #74
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
…gic; add count op; clean up
As a note, the diff isn't very clean in certain areas because I opted to make the method order in parity with the remote methods as well. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM. Just questions and a comment or two on style/tests
assertTrue(eventSemaphore?.tryAcquire(10, TimeUnit.SECONDS) ?: true) | ||
override fun waitForEvents(amount: Int) { | ||
waitLock.lock() | ||
changeEventListener.totalEventsToAccumulate = amount |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[q] Why is this here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is to give the harness the ability to wait for multiple events. The accumulator actually does nothing– could just be a counter– as the actual events are later accumulated via capturing the values from the spy.
This is being used to test bulk writes, as multiple change events are emitted from a single method call.
private val expectedEvent: ChangeEvent<BsonDocument>?, | ||
var emitEventSemaphore: Semaphore? | ||
) : ChangeEventListener<BsonDocument> { | ||
val eventAccumulator = mutableListOf<ChangeEvent<BsonDocument>>() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[q] Why was this added?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See answer above.
* Adds and returns a document with a new version to the given document. | ||
* | ||
* @param document the document to attach a new version to. | ||
* @param document the document to attach a new version to. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is checkstlye not catching any of these issues?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Checkstyle did not. The DataSynchronizer doesn't seem to actually be formatted, as far as I can tell, and my autoformatter "went to town" on the class. We should probably file a ticket (or just make a patch) to reformat it.
private static BsonDocument withNewVersion( | ||
final BsonDocument document, | ||
final BsonDocument newVersion | ||
final BsonDocument document, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why did this change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See above.
* Any local updates to this document cause it to be resumed. An example of pausing a document | ||
* is when a conflict is being resolved for that document and the handler throws an exception. | ||
* | ||
* <p> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Stray?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See above. Though technically, this is also fine.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
// else this is an update | ||
if (documentBeforeUpdate == null && updateOptions.isUpsert()) { | ||
config = syncConfig.addSynchronizedDocument(namespace, documentId); | ||
triggerListeningToNamespace(namespace); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Curious, do you have a test that checks that if you were to remove this line, it would fail?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't believe so– I can add a line in our new upsert tests that ensures that the stream is opened on the namespace.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added to testUpsertOne
and testUpsertMany
in DataSynchronizerUnitTests
👍
final BsonDocument beforeDocument; | ||
final BsonValue documentId = BsonUtils.getDocumentId(afterDocument); | ||
|
||
if ((beforeDocument = idToBeforeDocumentMap.get(documentId)) == null |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Comment here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
final BsonDocument beforeDocument; | ||
final BsonValue documentId = BsonUtils.getDocumentId(afterDocument); | ||
|
||
if ((beforeDocument = idToBeforeDocumentMap.get(documentId)) == null |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nit] Assigning to beforeDocument like this feels kind of odd as opposed to just doing it right before the if
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
excellent work! I mostly just have nits and minors but I think this will be LGTM once you address my comments
boolean resumeSyncForDocument(@NonNull final BsonValue documentId); | ||
|
||
/** | ||
* Counts the number of documents in the collection. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nit] should the comment be "Counts the number of documents in the collection that are synchronized with the remote collection"?
Task<Long> count(); | ||
|
||
/** | ||
* Counts the number of documents in the collection that have been synchronized from the remote |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nit] I think synchronized "with" remote would be more accurate than synchronized "from", since this may count local documents that haven't been synchronized to the remote, and therefore have never been synchronized "from" the remote
Task<Long> count(final Bson filter); | ||
|
||
/** | ||
* Counts the number of documents in the collection that have been synchronized from the remote |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nit] ditto
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
and pretty much everywhere in this file, "synchronized from" should become "synchronized with". not going to comment on this anymore
final Class<ResultT> resultClass); | ||
|
||
/** | ||
* Inserts the provided document. If the document is missing an identifier, the client should |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nit] it should generate one, but will it? if it is going to generate one it should say "If the document is missing an identifier, one will be generated."
|
||
import org.bson.conversions.Bson; | ||
|
||
class AggregateOperation<T> implements Operation<Collection<T>> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[minor] shouldn't this class be a SyncAggregateOperation
? AggregateOperation
already exists in another package which might get confusing. this also would make this have parity with the existing SyncFindOperation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Discussed offline. Because these are package private, we are going to keep these as is.
SyncFindOperation
will be whittled down to FindOperation
as well.
fun insertManyAndSync(documents: List<Document>): SyncInsertManyResult | ||
|
||
/** | ||
* Deletes a single document by the given id. It is first searched for in the local synchronized |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nit] this comment looks out of date. honestly this file probably doesn't need doc comments for the CRUD methods, this is pretty internal
return UpdateResult.acknowledged(0, 0L, null); | ||
void insertManyAndSync(final MongoNamespace namespace, | ||
final List<BsonDocument> documents) { | ||
getLocalCollection(namespace).insertMany(documents); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[alternatively] instead of generating IDs client-side, and depending on BsonUtils.getDocumentId
not failing we could look at the result of the local insertMany
(and insertOne
in insertOneAndSync
), and use the inserted ids there to update the doc config and emit the event
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should note that there are no results from local inserts.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The local client does not return a result.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
my mistake, I assumed it returned a an insertedId like Stitch
filter, | ||
update, | ||
new FindOneAndUpdateOptions() | ||
.collation(updateOptions.getCollation()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[minor] collation, bypassDocumentValidation, and arrayFilters are all unsupported by the remote MongoDB service so this might result in conflicts between local and remote where a document that is able to inserted locally is not able to be inserted remotely. we could either not support those options at all, or just do this with the assumption that we will eventually support the full mongodb surface area
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's no actual way for these to get passed downstream, if you look through the proxies– we do not actually support the options. But we seem to operate within the DataSynchronizer on localmdb without considering Remote.
|
||
@Test | ||
fun testDeleteOneById() { | ||
fun testUpsertOne() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nit] one other test that might be worth adding is to do an update where upsert is specified, but there are documents that match, and we want to verify that no insert occurs
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe that's covered here– but I can add another check to make it clearer.
} | ||
|
||
@Test | ||
fun testDeleteManyNoConflicts() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nit] might be worth throwing in an upsert test here
|
||
override fun insertOneAndSync(document: Document): RemoteInsertOneResult { | ||
override fun count(filter: Bson): Long { | ||
return Tasks.await(sync.count()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Even if not used currently, expected behavior would be for this to propagate the filter.
return Tasks.await(sync.count()) | |
return Tasks.await(sync.count(filter)) |
@adamchel PTAL |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
Ticket Description:
This should implement the same CRUD methods that the non sync service has. All of these methods should only operate on the synced, offline documents.
For replacements, they should use the same set and unset event optimization
insertMany
,updateMany
,deleteMany
,aggregate
, andcount
to the DataSynchronizer. This is to be in parity withRemoteMongoCollection
.a. For
deleteMany
andupdateMany
, the documents need to be fetched before the delete toretrieve the ids, due to the way canonical filters work.
b. For
updateMany
, this will match potential unmodified documents. Ignore those.c. For
updateMany
, it may be an upsert. If it is an upsert, filter the result using the upsertedIdand emit an insert.
findOneById
,updateOneById
anddeleteOneById
withfind
,updateOne
anddeleteOne
, respectively.a. For
deleteOne
, the document needs to be fetched before the delete to retrieve the id, due tothe way canonical filters work.
b. For
updateOne
, it may be an upsert. If it is an upsert, filter the result using the upsertedIdand emit an insert.
CoreSync
.AggregateOperation
,CoreSyncAggregateIterable
,CountOperation
,InsertManyOperation
, andDeleteManyOperation
classes to be instantiated in SyncOperations to maintain parity withOperations
(remote).SyncCountOptions
,SyncDeleteOptions
,SyncUpdateOptions
,SyncDeleteResult
,SyncInsertManyResult
,SyncInsertOneResult
, andSyncUpdateResult
to both maintain parity with the Remote*Options classes, and to create a divergence between our options and the local mongo client library.CoreSync
operations upwards throughSync
(server) andSync
(Android).Unit testing to test the new logic was critical here, especially for the bulk operations. Integration tests were limited to ensuring that each new operation operates as intended in an integrated environment. Since no new Sync cases were created, adding new conflicted/failing integration tests seemed unnecessarily cumbersome.