Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,11 @@
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.JsonNodeType;
import com.marklogic.client.*;
import com.marklogic.client.DatabaseClientFactory.HandleFactoryRegistry;
import com.marklogic.client.MarkLogicBindingException;
import com.marklogic.client.MarkLogicIOException;
import com.marklogic.client.MarkLogicInternalException;
import com.marklogic.client.Transaction;
import com.marklogic.client.document.DocumentWriteSet;
import com.marklogic.client.expression.PlanBuilder;
import com.marklogic.client.expression.PlanBuilder.Plan;
Expand All @@ -59,7 +62,6 @@ public class RowManagerImpl
private RowStructure rowStructureStyle = null;
private Integer optimize;
private String traceLabel;
private boolean update;

public RowManagerImpl(RESTServices services) {
super();
Expand Down Expand Up @@ -122,13 +124,7 @@ public void setOptimize(Integer value) {
this.optimize = value;
}

@Override
public RowManager withUpdate(boolean update) {
this.update = update;
return this;
}

@Override
@Override
public RawPlanDefinition newRawPlanDefinition(JSONWriteHandle handle) {
return new RawPlanDefinitionImpl(handle);
}
Expand Down Expand Up @@ -180,11 +176,7 @@ public <T extends StructureReadHandle> T resultDoc(Plan plan, T resultsHandle, T
.withColumnTypes(getDatatypeStyle())
.withOutput(getRowStructureStyle())
.getRequestParameters();
return services.postResource(requestLogger, determinePath(), transaction, params, astHandle, resultsHandle);
}

private String determinePath() {
return this.update ? "rows/update" : "rows";
return services.postResource(requestLogger, "rows", transaction, params, astHandle, resultsHandle);
}

@Override
Expand Down Expand Up @@ -299,9 +291,8 @@ public <T extends StructureReadHandle> T explain(Plan plan, T resultsHandle) {
RequestParameters params = new RequestParameters();
params.add("output", "explain");

return services.postResource(requestLogger, determinePath(), null, params, astHandle, resultsHandle);
return services.postResource(requestLogger, "rows", null, params, astHandle, resultsHandle);
}

@Override
public <T> T explainAs(Plan plan, Class<T> as) {
ContentHandle<T> handle = handleFor(as);
Expand Down Expand Up @@ -418,23 +409,11 @@ private <T extends AbstractReadHandle> String getRowFormat(T rowHandle) {
private RESTServiceResultIterator submitPlan(PlanBuilderBaseImpl.RequestPlan requestPlan, RequestParameters params, Transaction transaction) {
AbstractWriteHandle astHandle = requestPlan.getHandle();
List<ContentParam> contentParams = requestPlan.getContentParams();
final String path = determinePath();
try {
if (contentParams != null && !contentParams.isEmpty()) {
contentParams.add(new ContentParam(new PlanBuilderBaseImpl.PlanParamBase("query"), astHandle, null));
return services.postMultipartForm(requestLogger, path, transaction, params, contentParams);
}
return services.postIteratedResource(requestLogger, path, transaction, params, astHandle);
} catch (FailedRequestException ex) {
String message = ex.getMessage();
if (message != null && message.contains("RESTAPI-UPDATEFROMQUERY")) {
String betterMessage = "The Optic plan is attempting an update but was sent to the wrong REST API endpoint. " +
"You must invoke `withUpdate(true)` on the instance of com.marklogic.client.row.RowManager that you " +
"are using to submit the plan";
throw new FailedRequestException(betterMessage, ex.getFailedRequest());
}
throw ex;
}
if (contentParams != null && !contentParams.isEmpty()) {
contentParams.add(new ContentParam(new PlanBuilderBaseImpl.PlanParamBase("query"), astHandle, null));
return services.postMultipartForm(requestLogger, "rows", transaction, params, contentParams);
}
return services.postIteratedResource(requestLogger, "rows", transaction, params, astHandle);
}

private PlanBuilderBaseImpl.RequestPlan checkPlan(Plan plan) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,18 +87,6 @@ public enum RowStructure{ARRAY, OBJECT}
*/
void setTraceLabel(String label);

/**
* As of MarkLogic 11.2, the "v1/rows/update" endpoint must be used in order to submit an Optic plan that performs
* an update. This method must be called with a value of {@code true} in order for that endpoint to be used instead
* of "v1/rows". You may later call this method with a value of {@code false} in order to submit a plan that does
* not perform an update.
*
* @param update set to {@code true} if submitting a plan that performs an update
* @return the instance of this class
* @since 6.5.0
*/
RowManager withUpdate(boolean update);

/**
* @return the label that will be used for all log messages associated with the "optic" trace event
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public void setup() {
.evalAs(String.class);

Common.client = Common.newClientBuilder().withUsername("writer-no-default-permissions").build();
rowManager = Common.client.newRowManager().withUpdate(true);
rowManager = Common.client.newRowManager();
op = rowManager.newPlanBuilder();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public void insertDocsWithUserWithDefaultCollectionsAndPermissions() {
mapper.createObjectNode().put("hello", "two")));

Common.client = Common.newClientBuilder().withUsername(USER_WITH_DEFAULT_COLLECTIONS_AND_PERMISSIONS).build();
Common.client.newRowManager().withUpdate(true).execute(op
Common.client.newRowManager().execute(op
.fromDocDescriptors(op.docDescriptors(writeSet))
.write());

Expand Down Expand Up @@ -88,7 +88,7 @@ public void updateOnlyDocAsUserWithNoDefaults() {
public void updateOnlyDocWithUserWithDefaultCollectionsAndPermissions() {
// Set up client as user with default collections and permissions
Common.client = Common.newClientBuilder().withUsername(USER_WITH_DEFAULT_COLLECTIONS_AND_PERMISSIONS).build();
rowManager = Common.client.newRowManager().withUpdate(true);
rowManager = Common.client.newRowManager();
op = rowManager.newPlanBuilder();

verifyOnlyDocCanBeUpdatedWithoutLosingAnyMetadata();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.marklogic.client.test.rows;

import com.fasterxml.jackson.databind.node.ArrayNode;
import com.marklogic.client.FailedRequestException;
import com.marklogic.client.expression.PlanBuilder;
import com.marklogic.client.io.DocumentMetadataHandle;
import com.marklogic.client.io.JacksonHandle;
Expand All @@ -13,105 +12,89 @@

import java.util.List;

import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

@ExtendWith(RequiresML11.class)
public class LockForUpdateTest extends AbstractOpticUpdateTest {

@Test
public void basicTest() {
final String uri = "/acme/doc1.json";

// Write a document
rowManager.execute(op.fromDocDescriptors(
op.docDescriptor(newWriteOp(uri, new JacksonHandle(mapper.createObjectNode().put("hello", "world")))))
.write());
verifyJsonDoc(uri, doc -> assertEquals("world", doc.get("hello").asText()));

// Construct a plan that will lock the URI and update its collection
PlanBuilder.ModifyPlan plan = op
.fromDocDescriptors(
op.docDescriptor(newWriteOp(uri, new DocumentMetadataHandle().withCollections("optic1"), null))
)
.lockForUpdate()
.write(op.docCols(null, op.xs.stringSeq("uri", "collections")));

// Run an eval that locks the URI and sleeps for 2 seconds, which will block the plan run below
new Thread(() -> {
Common.newServerAdminClient().newServerEval()
.javascript(String.format("declareUpdate(); " +
"xdmp.lockForUpdate('%s'); " +
"xdmp.sleep(2000); " +
"xdmp.documentSetCollections('%s', ['eval1']);", uri, uri))
.evalAs(String.class);
}).start();

// Immediately run a plan that updates the collections as well; this should be blocked while the eval thread
// above completes
long start = System.currentTimeMillis();
rowManager.execute(plan);
long duration = System.currentTimeMillis() - start;
System.out.println("DUR: " + duration);

assertTrue(duration > 1500,
@Test
public void basicTest() {
final String uri = "/acme/doc1.json";

// Write a document
rowManager.execute(op.fromDocDescriptors(
op.docDescriptor(newWriteOp(uri, new JacksonHandle(mapper.createObjectNode().put("hello", "world")))))
.write());
verifyJsonDoc(uri, doc -> assertEquals("world", doc.get("hello").asText()));

// Construct a plan that will lock the URI and update its collection
PlanBuilder.ModifyPlan plan = op
.fromDocDescriptors(
op.docDescriptor(newWriteOp(uri, new DocumentMetadataHandle().withCollections("optic1"), null))
)
.lockForUpdate()
.write(op.docCols(null, op.xs.stringSeq("uri", "collections")));

// Run an eval that locks the URI and sleeps for 2 seconds, which will block the plan run below
new Thread(() -> {
Common.newServerAdminClient().newServerEval()
.javascript(String.format("declareUpdate(); " +
"xdmp.lockForUpdate('%s'); " +
"xdmp.sleep(2000); " +
"xdmp.documentSetCollections('%s', ['eval1']);", uri, uri))
.evalAs(String.class);
}).start();

// Immediately run a plan that updates the collections as well; this should be blocked while the eval thread
// above completes
long start = System.currentTimeMillis();
rowManager.execute(plan);
long duration = System.currentTimeMillis() - start;
System.out.println("DUR: " + duration);

assertTrue(duration > 1500,
"Because the eval call slept for 2 seconds, the duration of the plan execution should be at least " +
"1500ms, which is much longer than normal; it may not be at least 2 seconds due to the small delay in " +
"the Java layer of executing the plan; duration: " + duration);

// Verify that the collections were set based on the plan, which should have run second
verifyMetadata(uri, metadata -> {
DocumentMetadataHandle.DocumentCollections colls = metadata.getCollections();
assertEquals(1, colls.size());
assertEquals("optic1", colls.iterator().next());
});
}

@Test
void wrongEndpoint() {
rowManager.withUpdate(false);
FailedRequestException ex = assertThrows(
FailedRequestException.class,
() -> rowManager.execute(op.fromDocUris("/optic/test/musician1.json").lockForUpdate()),
"Hoping to update this assertion to verify that the server message tells the user to hit v1/rows/update " +
"instead; right now, it's mentioning using declareUpdate() which isn't applicable to a REST API user."
);

assertTrue(ex.getMessage().contains(
"The Optic plan is attempting an update but was sent to the wrong REST API endpoint. " +
"You must invoke `withUpdate(true)` on the instance of com.marklogic.client.row.RowManager " +
"that you are using to submit the plan."),
"Unexpected message: " + ex.getMessage());
}

@Test
public void uriColumnSpecified() {
List<RowRecord> rows = resultRows(op
.fromDocUris("/optic/test/musician1.json")
.lockForUpdate(op.col("uri")));
assertEquals(1, rows.size());
}

@Test
public void fromParamWithCustomUriColumn() {
ArrayNode paramValue = mapper.createArrayNode();
paramValue.addObject().put("myUri", "/optic/test/musician1.json");

List<RowRecord> rows = resultRows(op
.fromParam("bindingParam", "", op.colTypes(op.colType("myUri", "string")))
.lockForUpdate(op.col("myUri"))
.bindParam("bindingParam", new JacksonHandle(paramValue), null));
assertEquals(1, rows.size());
}

@Test
public void fromParamWithQualifiedUriColumn() {
ArrayNode paramValue = mapper.createArrayNode();
paramValue.addObject().put("myUri", "/optic/test/musician1.json");

List<RowRecord> rows = resultRows(op
.fromParam("bindingParam", "myQualifier", op.colTypes(op.colType("myUri", "string")))
.lockForUpdate(op.viewCol("myQualifier", "myUri"))
.bindParam("bindingParam", new JacksonHandle(paramValue), null));
assertEquals(1, rows.size());
}
// Verify that the collections were set based on the plan, which should have run second
verifyMetadata(uri, metadata -> {
DocumentMetadataHandle.DocumentCollections colls = metadata.getCollections();
assertEquals(1, colls.size());
assertEquals("optic1", colls.iterator().next());
});
}

@Test
public void uriColumnSpecified() {
List<RowRecord> rows = resultRows(op
.fromDocUris("/optic/test/musician1.json")
.lockForUpdate(op.col("uri")));
assertEquals(1, rows.size());
}

@Test
public void fromParamWithCustomUriColumn() {
ArrayNode paramValue = mapper.createArrayNode();
paramValue.addObject().put("myUri", "/optic/test/musician1.json");

List<RowRecord> rows = resultRows(op
.fromParam("bindingParam", "", op.colTypes(op.colType("myUri", "string")))
.lockForUpdate(op.col("myUri"))
.bindParam("bindingParam", new JacksonHandle(paramValue), null));
assertEquals(1, rows.size());
}

@Test
public void fromParamWithQualifiedUriColumn() {
ArrayNode paramValue = mapper.createArrayNode();
paramValue.addObject().put("myUri", "/optic/test/musician1.json");

List<RowRecord> rows = resultRows(op
.fromParam("bindingParam", "myQualifier", op.colTypes(op.colType("myUri", "string")))
.lockForUpdate(op.viewCol("myQualifier", "myUri"))
.bindParam("bindingParam", new JacksonHandle(paramValue), null));
assertEquals(1, rows.size());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,6 @@ void deleteNewMusician() {

@Test
void testResultRowsWithPointInTimeQueryTimestamp() {
rowManager.withUpdate(false);

final RawQueryDSLPlan plan = rowManager.newRawQueryDSLPlan(new StringHandle("op.fromView('opticUnitTest', 'musician_ml10')"));

JacksonHandle result = new JacksonHandle();
Expand Down
Loading