diff --git a/marklogic-client-api/src/main/java/com/marklogic/client/impl/RowManagerImpl.java b/marklogic-client-api/src/main/java/com/marklogic/client/impl/RowManagerImpl.java index 23c6bb2a2..b85ba6b80 100644 --- a/marklogic-client-api/src/main/java/com/marklogic/client/impl/RowManagerImpl.java +++ b/marklogic-client-api/src/main/java/com/marklogic/client/impl/RowManagerImpl.java @@ -226,7 +226,7 @@ public void execute(Plan plan, Transaction transaction) { @Override public RowSet resultRows(Plan plan, T rowHandle) { - return resultRows(plan, rowHandle, (Transaction) null); + return resultRows(plan, rowHandle, null); } @Override @@ -236,13 +236,18 @@ public RowSet resultRows(Plan plan, T rowHand String rowFormat = getRowFormat(rowHandle); PlanBuilderBaseImpl.RequestPlan requestPlan = checkPlan(plan); - RequestParameters params = newRowsParamsBuilder(requestPlan) - .withRowFormat(rowFormat) - .withNodeColumns("inline") - .withColumnTypes(datatypeStyle) - .withOutput(rowStructureStyle) - .getRequestParameters(); + RowsParamsBuilder rowsParamsBuilder = newRowsParamsBuilder(requestPlan) + .withRowFormat(rowFormat) + .withNodeColumns("inline") + .withColumnTypes(datatypeStyle) + .withOutput(rowStructureStyle); + + if (rowHandle instanceof BaseHandle) { + rowsParamsBuilder.withTimestamp(((BaseHandle)rowHandle).getPointInTimeQueryTimestamp()); + } + + RequestParameters params = rowsParamsBuilder.getRequestParameters(); RESTServiceResultIterator iter = submitPlan(requestPlan, params, transaction); RowSetHandle rowset = new RowSetHandle<>(rowFormat, datatypeStyle, rowStructureStyle, iter, rowHandle); rowset.init(); diff --git a/marklogic-client-api/src/main/java/com/marklogic/client/impl/RowsParamsBuilder.java b/marklogic-client-api/src/main/java/com/marklogic/client/impl/RowsParamsBuilder.java index 98828493d..ce3498a49 100644 --- a/marklogic-client-api/src/main/java/com/marklogic/client/impl/RowsParamsBuilder.java +++ b/marklogic-client-api/src/main/java/com/marklogic/client/impl/RowsParamsBuilder.java @@ -91,7 +91,14 @@ public RowsParamsBuilder withTraceLabel(String label) { return this; } + public RowsParamsBuilder withTimestamp(long serverTimestamp) { + if (serverTimestamp > 0) { + params.add("timestamp", serverTimestamp + ""); + } + return this; + } + public RequestParameters getRequestParameters() { return this.params; } -} \ No newline at end of file +} diff --git a/marklogic-client-api/src/test/java/com/marklogic/client/test/rows/ResultRowsWithTimestampTest.java b/marklogic-client-api/src/test/java/com/marklogic/client/test/rows/ResultRowsWithTimestampTest.java new file mode 100644 index 000000000..728abcf0e --- /dev/null +++ b/marklogic-client-api/src/test/java/com/marklogic/client/test/rows/ResultRowsWithTimestampTest.java @@ -0,0 +1,69 @@ +package com.marklogic.client.test.rows; + +import com.fasterxml.jackson.databind.JsonNode; +import com.marklogic.client.io.JacksonHandle; +import com.marklogic.client.io.StringHandle; +import com.marklogic.client.row.RawQueryDSLPlan; +import com.marklogic.client.test.Common; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import java.util.Iterator; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class ResultRowsWithTimestampTest extends AbstractOpticUpdateTest { + + private final static String NEW_MUSICIAN_URI = "/test/newMusician.json"; + + private final static String NEW_MUSICIAN_JSON = "{\n" + + " \"musician\": {\n" + + " \"lastName\": \"Smith\",\n" + + " \"firstName\": \"Jane\",\n" + + " \"dob\": \"1901-08-04\"\n" + + " }\n" + + "}"; + + @AfterEach + void deleteNewMusician() { + Common.client.newJSONDocumentManager().delete(NEW_MUSICIAN_URI); + } + + @Test + void testResultRowsWithPointInTimeQueryTimestamp() { + final RawQueryDSLPlan plan = rowManager.newRawQueryDSLPlan(new StringHandle("op.fromView('opticUnitTest', 'musician')")); + + JacksonHandle result = new JacksonHandle(); + JsonNode doc = rowManager.resultDoc(plan, result).get(); + assertEquals(4, doc.get("rows").size(), "Expecting the 4 musicians loaded by test-app to exist"); + + final long serverTimestamp = result.getServerTimestamp(); + assertTrue(serverTimestamp > 0, "Unexpected timestamp: " + serverTimestamp); + + // Insert a new musician, which will bump up the server timestamp + Common.client.newJSONDocumentManager().write(NEW_MUSICIAN_URI, newDefaultMetadata(), new StringHandle(NEW_MUSICIAN_JSON)); + + doc = rowManager.resultDoc(plan, new JacksonHandle()).get(); + assertEquals(5, doc.get("rows").size(), "Should now get 5 musician rows due to the 5th row being added by " + + "inserting the new musician doc"); + + // Now verify a point-in-time query works + result = new JacksonHandle(); + result.setPointInTimeQueryTimestamp(serverTimestamp); + doc = rowManager.resultDoc(plan, result).get(); + assertEquals(4, doc.get("rows").size(), "Only 4 rows should be returned since the query should have been " + + "run at a server timestamp prior to the newMusician doc being inserted."); + + // And verify point-in-time works when using resultRows too + result = new JacksonHandle(); + result.setPointInTimeQueryTimestamp(serverTimestamp); + Iterator rows = rowManager.resultRows(plan, result).iterator(); + int count = 0; + while (rows.hasNext()) { + rows.next(); + count++; + } + assertEquals(4, count, "resultRows should honor the point-in-time timestamp, just like resultDoc does"); + } +}