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 @@ -75,7 +75,7 @@ void setHandleRegistry(HandleFactoryRegistry handleRegistry) {
this.handleRegistry = handleRegistry;
}

@Override
@Override
public PlanBuilder newPlanBuilder() {
PlanBuilderImpl planBuilder = new PlanBuilderSubImpl();

Expand Down Expand Up @@ -211,7 +211,7 @@ public RowSet<RowRecord> resultRows(Plan plan, Transaction transaction) {
public void execute(Plan plan) {
execute(plan, null);
}

@Override
public void execute(Plan plan, Transaction transaction) {
PlanBuilderBaseImpl.RequestPlan requestPlan = checkPlan(plan);
Expand Down Expand Up @@ -268,7 +268,7 @@ public <T> RowSet<T> resultRowsAs(Plan plan, Class<T> as, Transaction transactio
.withColumnTypes(datatypeStyle)
.withOutput(rowStructureStyle)
.getRequestParameters();

RESTServiceResultIterator iter = submitPlan(requestPlan, params, transaction);
RowSetObject<T> rowset = new RowSetObject<>(rowFormat, datatypeStyle, rowStructureStyle, iter, rowHandle);
rowset.init();
Expand Down Expand Up @@ -356,7 +356,31 @@ public <T> T columnInfoAs(PlanBuilder.PreparePlan plan, Class<T> as) {
return handle.get();
}

private <T extends AbstractReadHandle> String getRowFormat(T rowHandle) {
@Override
public <T extends JSONReadHandle> T graphql(JSONWriteHandle query, T resultsHandle) {
if (resultsHandle == null) {
throw new IllegalArgumentException("Must specify a handle for the results of the GraphQL query");
}
RequestParameters params = new RequestParameters();
// Must force the MIME type before passing this to OkHttpServices - which does the exact same check below,
// requiring that the query handle be an instance of HandleImplementation.
HandleAccessor.checkHandle(query, "write").setMimetype("application/graphql");
return services.postResource(requestLogger, "rows/graphql", null, params, query, resultsHandle);
}

@Override
public <T> T graphqlAs(JSONWriteHandle query, Class<T> as) {
ContentHandle<T> handle = handleFor(as);
if (!(handle instanceof JSONReadHandle)) {
throw new IllegalArgumentException("The handle is not an instance of JSONReadHandle.");
}
if (graphql(query, (JSONReadHandle) handle) == null) {
return null;
}
return handle.get();
}

private <T extends AbstractReadHandle> String getRowFormat(T rowHandle) {
if (rowHandle == null) {
throw new IllegalArgumentException("Must specify a handle to iterate over the rows");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,29 +53,29 @@ public enum RowStructure{ARRAY, OBJECT}
/**
* Specifies whether to emit the data type of each column in each row or only in the header
* in the response for requests made with the row manager.
*
*
* The distinction is significant when getting the rows as JSON or XML.
* Because the server supports columns with variant data types, the default is in each row.
* You can configure the row manager to return more concise objects if the data type
* is consistent or if you aren't using the data type.
*
*
* @param style the part of the rowset that should contain data types
*/
void setDatatypeStyle(RowSetPart style);

/**
* Returns whether each row should have an array or object structure
* Returns whether each row should have an array or object structure
* in the response for requests made with the row manager.
* @return the style of row structure
*/
RowStructure getRowStructureStyle();
/**
* Specifies whether to get each row as an object (the default) or as an array
* in the response for requests made with the row manager.
*
*
* The distinction is significant when getting the rows as JSON
* and also when executing a map or reduce function on the server.
*
*
* @param style the structure of rows in the response
*/
void setRowStructureStyle(RowStructure style);
Expand Down Expand Up @@ -130,14 +130,14 @@ public enum RowStructure{ARRAY, OBJECT}

/**
* Execute the given plan without returning any result.
*
*
* @param plan the definition of a plan
*/
void execute(Plan plan);

/**
* Execute the given plan without returning any result.
*
*
* @param plan the definition of a plan
* @param transaction a open transaction for the execute operation to run within
*/
Expand Down Expand Up @@ -170,7 +170,7 @@ public enum RowStructure{ARRAY, OBJECT}
<T extends StructureReadHandle> RowSet<T> resultRows(Plan plan, T rowHandle);
/**
* Constructs and retrieves a set of database rows based on a plan using
* a JSON or XML handle for each row and reflecting documents written or
* a JSON or XML handle for each row and reflecting documents written or
* deleted by an uncommitted transaction.
* @param plan the definition of a plan for the database rows
* @param rowHandle the JSON or XML handle that provides each row
Expand All @@ -182,15 +182,15 @@ public enum RowStructure{ARRAY, OBJECT}

/**
* Constructs and retrieves a set of database rows based on a plan using
* a JSON or XML handle for each row and reflecting documents written or
* a JSON or XML handle for each row and reflecting documents written or
* deleted by an uncommitted transaction.
*
*
* The IO class must have been registered before creating the database client.
* By default, the provided handles that implement
* By default, the provided handles that implement
* {@link com.marklogic.client.io.marker.ContentHandle ContentHandle} are registered.
*
*
* <a href="../../../../overview-summary.html#ShortcutMethods">Learn more about shortcut methods</a>
*
*
* @param plan the definition of a plan for the database rows
* @param as the IO class for reading each row as JSON or XML content
* @param <T> the type of object that will be returned by the handle registered for it
Expand All @@ -199,15 +199,15 @@ public enum RowStructure{ARRAY, OBJECT}
<T> RowSet<T> resultRowsAs(Plan plan, Class<T> as);
/**
* Constructs and retrieves a set of database rows based on a plan using
* a JSON or XML handle for each row and reflecting documents written or
* a JSON or XML handle for each row and reflecting documents written or
* deleted by an uncommitted transaction.
*
*
* The IO class must have been registered before creating the database client.
* By default, the provided handles that implement
* By default, the provided handles that implement
* {@link com.marklogic.client.io.marker.ContentHandle ContentHandle} are registered.
*
*
* <a href="../../../../overview-summary.html#ShortcutMethods">Learn more about shortcut methods</a>
*
*
* @param plan the definition of a plan for the database rows
* @param as the IO class for reading each row as JSON or XML content
* @param transaction a open transaction for documents from which rows have been projected
Expand Down Expand Up @@ -240,13 +240,13 @@ public enum RowStructure{ARRAY, OBJECT}
/**
* Constructs and retrieves a set of database rows based on a plan
* in the representation specified by the IO class.
*
*
* The IO class must have been registered before creating the database client.
* By default, the provided handles that implement
* By default, the provided handles that implement
* {@link com.marklogic.client.io.marker.ContentHandle ContentHandle} are registered.
*
*
* <a href="../../../../overview-summary.html#ShortcutMethods">Learn more about shortcut methods</a>
*
*
* @param plan the definition of a plan for the database rows
* @param as the IO class for reading the set of rows
* @param <T> the type of the IO object for reading the set of rows
Expand All @@ -257,13 +257,13 @@ public enum RowStructure{ARRAY, OBJECT}
* Constructs and retrieves a set of database rows based on a plan
* in the representation specified by the IO class and reflecting
* documents written or deleted by an uncommitted transaction.
*
*
* The IO class must have been registered before creating the database client.
* By default, the provided handles that implement
* By default, the provided handles that implement
* {@link com.marklogic.client.io.marker.ContentHandle ContentHandle} are registered.
*
*
* <a href="../../../../overview-summary.html#ShortcutMethods">Learn more about shortcut methods</a>
*
*
* @param plan the definition of a plan for the database rows
* @param as the IO class for reading the set of rows
* @param transaction a open transaction for documents from which rows have been projected
Expand All @@ -284,13 +284,13 @@ public enum RowStructure{ARRAY, OBJECT}
/**
* Constructs a plan for retrieving a set of database rows and returns an explanation
* of the plan in the representation specified by the IO class.
*
*
* The IO class must have been registered before creating the database client.
* By default, the provided handles that implement
* By default, the provided handles that implement
* {@link com.marklogic.client.io.marker.ContentHandle ContentHandle} are registered.
*
*
* <a href="../../../../overview-summary.html#ShortcutMethods">Learn more about shortcut methods</a>
*
*
* @param plan the definition of a plan for database rows
* @param as the IO class for reading the explanation for the plan
* @param <T> the type of the IO object for reading the explanation
Expand Down Expand Up @@ -361,4 +361,24 @@ public enum RowStructure{ARRAY, OBJECT}
* @return an object of the IO class with the content of the column information for the plan
*/
<T> T columnInfoAs(PlanBuilder.PreparePlan plan, Class<T> as);

/**
* Executes a GraphQL query against the database and returns the results as a JSON object.
*
* @param query the GraphQL query to execute
* @param resultsHandle the IO class for capturing the results
* @param <T> the type of the IO object for r the results
* @return an object of the IO class containing the query results, which will include error messages if the query fails
*/
<T extends JSONReadHandle> T graphql(JSONWriteHandle query, T resultsHandle);

/**
* Executes a GraphQL query against the database and returns the results as a JSON object.
*
* @param query the GraphQL query to execute
* @param as the class type of the results to return; typically JsonNode or String
* @param <T> the type of the results to return
* @return an instance of the given return type that contains the query results, which will include error messages if the query fails
*/
<T> T graphqlAs(JSONWriteHandle query, Class<T> as);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package com.marklogic.client.test.rows;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.marklogic.client.io.JacksonHandle;
import com.marklogic.client.io.StringHandle;
import com.marklogic.client.row.RowManager;
import com.marklogic.client.test.Common;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

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

public class GraphQLTest extends AbstractOpticUpdateTest {

private final static String QUERY = "query myQuery { opticUnitTest_musician {firstName lastName}}";

private RowManager rowManager;

@BeforeEach
void beforeEach() {
rowManager = Common.newClient().newRowManager();
}

@Test
void jsonQuery() {
ObjectNode query = mapper.createObjectNode().put("query", QUERY);
JsonNode response = rowManager.graphql(new JacksonHandle(query), new JacksonHandle()).get();
verifyResponse(response);
}

@Test
void stringQuery() {
JsonNode response = rowManager.graphql(new StringHandle("{\"query\": \"" + QUERY + "\"}"), new JacksonHandle()).get();
verifyResponse(response);
}

@Test
void getResultAsJson() {
ObjectNode query = mapper.createObjectNode().put("query", QUERY);
JsonNode response = rowManager.graphqlAs(new JacksonHandle(query), JsonNode.class);
verifyResponse(response);
}

@Test
void getResultAsString() throws Exception {
ObjectNode query = mapper.createObjectNode().put("query", QUERY);
String response = rowManager.graphqlAs(new JacksonHandle(query), String.class);
verifyResponse(mapper.readTree(response));
}

@Test
void invalidQuery() {
ObjectNode query = mapper.createObjectNode().put("query", "this is not valid");
JsonNode response = rowManager.graphql(new JacksonHandle(query), new JacksonHandle()).get();

assertTrue(response.has("errors"));
assertEquals(1, response.get("errors").size());

String message = response.get("errors").get(0).get("message").asText();
assertTrue(message.startsWith("GRAPHQL-PARSE: Error parsing the GraphQL request string"),
"Unexpected error message: " + message);
}

private void verifyResponse(JsonNode response) {
JsonNode data = response.get("data");
JsonNode musicians = data.get("opticUnitTest_musician");
assertEquals(4, musicians.size());
musicians.forEach(musician -> {
assertEquals(2, musician.size());
assertTrue(musician.has("firstName"));
assertTrue(musician.has("lastName"));
});
}
}