Skip to content

Commit

Permalink
split ChangeStreamCursor from OplogCursor
Browse files Browse the repository at this point in the history
  • Loading branch information
jmoghisi committed Apr 6, 2021
1 parent 250e019 commit 9be4583
Show file tree
Hide file tree
Showing 8 changed files with 195 additions and 111 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -644,7 +644,7 @@ private Document commandChangeStreamPipeline(Document query, Oplog oplog, String
int batchSize = (int) cursorDocument.getOrDefault("batchSize", 0);

String namespace = getFullCollectionNamespace(collectionName);
Cursor cursor = oplog.createCursor(changeStreamDocument, namespace, aggregation);
Cursor cursor = oplog.createChangeStreamCursor(changeStreamDocument, namespace, aggregation);
return Utils.firstBatchCursorResponse(namespace, cursor.takeDocuments(batchSize), cursor);
}

Expand Down
140 changes: 140 additions & 0 deletions core/src/main/java/de/bwaldvogel/mongo/oplog/ChangeStreamCursor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package de.bwaldvogel.mongo.oplog;

import java.util.List;
import java.util.stream.Collectors;

import de.bwaldvogel.mongo.MongoBackend;
import de.bwaldvogel.mongo.backend.TailableCursor;
import de.bwaldvogel.mongo.backend.aggregation.Aggregation;
import de.bwaldvogel.mongo.bson.BsonTimestamp;
import de.bwaldvogel.mongo.bson.Document;
import de.bwaldvogel.mongo.exception.MongoServerException;

public class ChangeStreamCursor implements TailableCursor {

private static final String FULL_DOCUMENT = "fullDocument";

private final MongoBackend mongoBackend;
private final Document changeStreamDocument;
private final Aggregation aggregation;
private final OplogCursor oplogCursor;

ChangeStreamCursor(
MongoBackend mongoBackend,
Document changeStreamDocument,
Aggregation aggregation,
OplogCursor oplogCursor
) {
this.mongoBackend = mongoBackend;
this.changeStreamDocument = changeStreamDocument;
this.aggregation = aggregation;
this.oplogCursor = oplogCursor;
}

@Override
public long getId() {
return oplogCursor.getId();
}

@Override
public boolean isEmpty() {
return oplogCursor.isEmpty();
}

@Override
public List<Document> takeDocuments(int numberToReturn) {
return aggregation.runStagesAsStream(
oplogCursor.takeDocuments(numberToReturn).stream()
.map(this::toChangeStreamResponseDocument)
).collect(Collectors.toList());
}

@Override
public OplogPosition getPosition() {
return oplogCursor.getPosition();
}

private Document toChangeStreamResponseDocument(Document oplogDocument) {
OperationType operationType = OperationType.fromCode(oplogDocument.get("op").toString());
Document documentKey = new Document();
Document document = getUpdateDocument(oplogDocument);
BsonTimestamp timestamp = OplogUtils.getOplogTimestamp(oplogDocument);
OplogPosition oplogPosition = new OplogPosition(timestamp);
switch (operationType) {
case UPDATE:
case DELETE:
documentKey = document;
break;
case INSERT:
documentKey.append(OplogDocumentFields.ID, document.get(OplogDocumentFields.ID));
break;
case COMMAND:
return toChangeStreamCommandResponseDocument(oplogDocument, oplogPosition, timestamp);
default:
throw new IllegalArgumentException("Unexpected operation type: " + operationType);
}

return new Document()
.append(OplogDocumentFields.ID, new Document(OplogDocumentFields.ID_DATA_KEY, oplogPosition.toHexString()))
.append("operationType", operationType.getDescription())
.append(FULL_DOCUMENT, getFullDocument(changeStreamDocument, oplogDocument, operationType))
.append("documentKey", documentKey)
.append("clusterTime", timestamp);
}

private Document toChangeStreamCommandResponseDocument(Document oplogDocument, OplogPosition oplogPosition, BsonTimestamp timestamp) {
Document document = getUpdateDocument(oplogDocument);
String operationType = document.keySet().stream().findFirst().orElseThrow(
() -> new MongoServerException("Unspecified command operation type")
);

return new Document()
.append(OplogDocumentFields.ID, new Document(OplogDocumentFields.ID_DATA_KEY, oplogPosition.toHexString()))
.append("operationType", operationType)
.append("clusterTime", timestamp);
}

private Document getFullDocument(Document changeStreamDocument, Document document, OperationType operationType) {
switch (operationType) {
case INSERT:
return getUpdateDocument(document);
case DELETE:
return null;
case UPDATE:
return lookUpUpdateDocument(changeStreamDocument, document);
}
throw new IllegalArgumentException("Invalid operation type");
}

private Document getUpdateDocument(Document document) {
return (Document) document.get(OplogDocumentFields.O);
}

private Document lookUpUpdateDocument(Document changeStreamDocument, Document document) {
Document deltaUpdate = getDeltaUpdate(getUpdateDocument(document));
if (changeStreamDocument.containsKey(FULL_DOCUMENT) && changeStreamDocument.get(FULL_DOCUMENT).equals("updateLookup")) {
String namespace = (String) document.get(OplogDocumentFields.NAMESPACE);
String databaseName = namespace.split("\\.")[0];
String collectionName = namespace.split("\\.")[1];
return mongoBackend.resolveDatabase(databaseName)
.resolveCollection(collectionName, true)
.queryAllAsStream()
.filter(d -> d.get(OplogDocumentFields.ID).equals(((Document) document.get(OplogDocumentFields.O2)).get(OplogDocumentFields.ID)))
.findFirst()
.orElse(deltaUpdate);
}
return deltaUpdate;
}

private Document getDeltaUpdate(Document updateDocument) {
Document delta = new Document();
if (updateDocument.containsKey("$set")) {
delta.appendAll((Document) updateDocument.get("$set"));
}
if (updateDocument.containsKey("$unset")) {
delta.appendAll((Document) updateDocument.get("$unset"));
}
return delta;
}

}
125 changes: 23 additions & 102 deletions core/src/main/java/de/bwaldvogel/mongo/oplog/CollectionBackedOplog.java
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
package de.bwaldvogel.mongo.oplog;

import java.util.List;
import java.util.Objects;
import java.util.UUID;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import de.bwaldvogel.mongo.MongoBackend;
import de.bwaldvogel.mongo.MongoCollection;
import de.bwaldvogel.mongo.backend.Cursor;
import de.bwaldvogel.mongo.backend.CursorRegistry;
import de.bwaldvogel.mongo.backend.TailableCursor;
import de.bwaldvogel.mongo.backend.Utils;
import de.bwaldvogel.mongo.backend.aggregation.Aggregation;
import de.bwaldvogel.mongo.bson.BsonTimestamp;
import de.bwaldvogel.mongo.bson.Document;
import de.bwaldvogel.mongo.exception.MongoServerException;

public class CollectionBackedOplog implements Oplog {

Expand Down Expand Up @@ -83,21 +84,19 @@ public void handleDropCollection(String namespace) {
collection.addDocument(toOplogDropCollection(databaseName, collectionName));
}

private Stream<Document> streamOplog(Document changeStreamDocument, OplogPosition position, Aggregation aggregation,
String namespace) {
return aggregation.runStagesAsStream(collection.queryAllAsStream()
private Stream<Document> streamOplog(OplogPosition position, String namespace) {
return collection.queryAllAsStream()
.filter(document -> filterNamespace(document, namespace))
.filter(document -> {
BsonTimestamp timestamp = getOplogTimestamp(document);
BsonTimestamp timestamp = OplogUtils.getOplogTimestamp(document);
OplogPosition documentOplogPosition = new OplogPosition(timestamp);
return documentOplogPosition.isAfter(position);
})
.sorted((o1, o2) -> {
BsonTimestamp timestamp1 = getOplogTimestamp(o1);
BsonTimestamp timestamp2 = getOplogTimestamp(o2);
BsonTimestamp timestamp1 = OplogUtils.getOplogTimestamp(o1);
BsonTimestamp timestamp2 = OplogUtils.getOplogTimestamp(o2);
return timestamp1.compareTo(timestamp2);
})
.map(document -> toChangeStreamResponseDocument(document, changeStreamDocument)));
});
}

private static boolean filterNamespace(Document document, String namespace) {
Expand All @@ -110,7 +109,16 @@ private static boolean filterNamespace(Document document, String namespace) {
}

@Override
public Cursor createCursor(Document changeStreamDocument, String namespace, Aggregation aggregation) {
public OplogCursor createCursor(String namespace, OplogPosition initialOplogPosition) {
return new OplogCursor(
cursorRegistry.generateCursorId(),
position -> streamOplog(position, namespace),
initialOplogPosition
);
}

@Override
public TailableCursor createChangeStreamCursor(Document changeStreamDocument, String namespace, Aggregation aggregation) {
Document startAfter = (Document) changeStreamDocument.get(START_AFTER);
Document resumeAfter = (Document) changeStreamDocument.get(RESUME_AFTER);
BsonTimestamp startAtOperationTime = (BsonTimestamp) changeStreamDocument.get(START_AT_OPERATION_TIME);
Expand All @@ -123,7 +131,7 @@ public Cursor createCursor(Document changeStreamDocument, String namespace, Aggr
String collectionName = Utils.getCollectionNameFromFullName(namespace);
boolean resumeAfterTerminalEvent = collection.queryAllAsStream()
.filter(document -> {
BsonTimestamp timestamp = getOplogTimestamp(document);
BsonTimestamp timestamp = OplogUtils.getOplogTimestamp(document);
OplogPosition documentOplogPosition = new OplogPosition(timestamp);
return initialOplogPosition.isAfter(documentOplogPosition.inclusive());
})
Expand All @@ -141,9 +149,9 @@ public Cursor createCursor(Document changeStreamDocument, String namespace, Aggr
initialOplogPosition = new OplogPosition(oplogClock.now());
}

Function<OplogPosition, Stream<Document>> streamSupplier =
position -> streamOplog(changeStreamDocument, position, aggregation, namespace);
OplogCursor cursor = new OplogCursor(cursorRegistry.generateCursorId(), streamSupplier, initialOplogPosition);
OplogCursor oplogCursor = createCursor(namespace, initialOplogPosition);
ChangeStreamCursor cursor
= new ChangeStreamCursor(backend, changeStreamDocument, aggregation, oplogCursor);
cursorRegistry.add(cursor);
return cursor;
}
Expand Down Expand Up @@ -185,91 +193,4 @@ private boolean isOplogCollection(String namespace) {
return collection.getFullName().equals(namespace);
}

private Document getFullDocument(Document changeStreamDocument, Document document, OperationType operationType) {
switch (operationType) {
case INSERT:
return getUpdateDocument(document);
case DELETE:
return null;
case UPDATE:
return lookUpUpdateDocument(changeStreamDocument, document);
}
throw new IllegalArgumentException("Invalid operation type");
}

private Document lookUpUpdateDocument(Document changeStreamDocument, Document document) {
Document deltaUpdate = getDeltaUpdate(getUpdateDocument(document));
if (changeStreamDocument.containsKey(FULL_DOCUMENT) && changeStreamDocument.get(FULL_DOCUMENT).equals("updateLookup")) {
String namespace = (String) document.get(OplogDocumentFields.NAMESPACE);
String databaseName = namespace.split("\\.")[0];
String collectionName = namespace.split("\\.")[1];
return backend.resolveDatabase(databaseName)
.resolveCollection(collectionName, true)
.queryAllAsStream()
.filter(d -> d.get(OplogDocumentFields.ID).equals(((Document) document.get(OplogDocumentFields.O2)).get(OplogDocumentFields.ID)))
.findFirst()
.orElse(deltaUpdate);
}
return deltaUpdate;
}

private Document getDeltaUpdate(Document updateDocument) {
Document delta = new Document();
if (updateDocument.containsKey("$set")) {
delta.appendAll((Document) updateDocument.get("$set"));
}
if (updateDocument.containsKey("$unset")) {
delta.appendAll((Document) updateDocument.get("$unset"));
}
return delta;
}

private Document toChangeStreamResponseDocument(Document oplogDocument, Document changeStreamDocument) {
OperationType operationType = OperationType.fromCode(oplogDocument.get(OplogDocumentFields.OPERATION_TYPE).toString());
Document documentKey = new Document();
Document document = getUpdateDocument(oplogDocument);
BsonTimestamp timestamp = getOplogTimestamp(oplogDocument);
OplogPosition oplogPosition = new OplogPosition(timestamp);
switch (operationType) {
case UPDATE:
case DELETE:
documentKey = document;
break;
case INSERT:
documentKey.append(OplogDocumentFields.ID, document.get(OplogDocumentFields.ID));
break;
case COMMAND:
return toChangeStreamCommandResponseDocument(oplogDocument, oplogPosition, timestamp);
default:
throw new IllegalArgumentException("Unexpected operation type: " + operationType);
}

return new Document()
.append(OplogDocumentFields.ID, new Document(OplogDocumentFields.ID_DATA_KEY, oplogPosition.toHexString()))
.append(OPERATION_TYPE, operationType.getDescription())
.append(FULL_DOCUMENT, getFullDocument(changeStreamDocument, oplogDocument, operationType))
.append(DOCUMENT_KEY, documentKey)
.append(CLUSTER_TIME, timestamp);
}

private Document toChangeStreamCommandResponseDocument(Document oplogDocument, OplogPosition oplogPosition, BsonTimestamp timestamp) {
Document document = getUpdateDocument(oplogDocument);
String operationType = document.keySet().stream().findFirst().orElseThrow(
() -> new MongoServerException("Unspecified command operation type")
);

return new Document()
.append(OplogDocumentFields.ID, new Document(OplogDocumentFields.ID_DATA_KEY, oplogPosition.toHexString()))
.append(OPERATION_TYPE, operationType)
.append(CLUSTER_TIME, timestamp);
}

private static BsonTimestamp getOplogTimestamp(Document document) {
return (BsonTimestamp) document.get(OplogDocumentFields.TIMESTAMP);
}

private static Document getUpdateDocument(Document document) {
return (Document) document.get(OplogDocumentFields.O);
}

}
9 changes: 7 additions & 2 deletions core/src/main/java/de/bwaldvogel/mongo/oplog/NoopOplog.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

import java.util.List;

import de.bwaldvogel.mongo.backend.Cursor;
import de.bwaldvogel.mongo.backend.EmptyCursor;
import de.bwaldvogel.mongo.backend.TailableCursor;
import de.bwaldvogel.mongo.backend.aggregation.Aggregation;
import de.bwaldvogel.mongo.bson.Document;

Expand Down Expand Up @@ -35,7 +35,12 @@ public void handleDropCollection(String namespace) {
}

@Override
public Cursor createCursor(Document changeStreamDocument, String namespace, Aggregation aggregation) {
public TailableCursor createCursor(String namespace, OplogPosition initialOplogPosition) {
return EmptyCursor.get();
}

@Override
public TailableCursor createChangeStreamCursor(Document changeStreamDocument, String namespace, Aggregation aggregation) {
return EmptyCursor.get();
}
}
6 changes: 4 additions & 2 deletions core/src/main/java/de/bwaldvogel/mongo/oplog/Oplog.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import java.util.List;

import de.bwaldvogel.mongo.backend.Cursor;
import de.bwaldvogel.mongo.backend.TailableCursor;
import de.bwaldvogel.mongo.backend.aggregation.Aggregation;
import de.bwaldvogel.mongo.bson.Document;

Expand All @@ -16,5 +16,7 @@ public interface Oplog {

void handleDropCollection(String namespace);

Cursor createCursor(Document changeStreamDocument, String namespace, Aggregation aggregation);
TailableCursor createCursor(String namespace, OplogPosition initialOplogPosition);

TailableCursor createChangeStreamCursor(Document changeStreamDocument, String namespace, Aggregation aggregation);
}
Loading

0 comments on commit 9be4583

Please sign in to comment.