Skip to content

Commit 157c345

Browse files
authored
Merge pull request #1545 from marklogic/feature/357-graphql
DEVEXP-357 Can now submit GraphQL queries
2 parents 6bf2683 + 8cc0e17 commit 157c345

File tree

3 files changed

+154
-34
lines changed

3 files changed

+154
-34
lines changed

marklogic-client-api/src/main/java/com/marklogic/client/impl/RowManagerImpl.java

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ void setHandleRegistry(HandleFactoryRegistry handleRegistry) {
7575
this.handleRegistry = handleRegistry;
7676
}
7777

78-
@Override
78+
@Override
7979
public PlanBuilder newPlanBuilder() {
8080
PlanBuilderImpl planBuilder = new PlanBuilderSubImpl();
8181

@@ -211,7 +211,7 @@ public RowSet<RowRecord> resultRows(Plan plan, Transaction transaction) {
211211
public void execute(Plan plan) {
212212
execute(plan, null);
213213
}
214-
214+
215215
@Override
216216
public void execute(Plan plan, Transaction transaction) {
217217
PlanBuilderBaseImpl.RequestPlan requestPlan = checkPlan(plan);
@@ -268,7 +268,7 @@ public <T> RowSet<T> resultRowsAs(Plan plan, Class<T> as, Transaction transactio
268268
.withColumnTypes(datatypeStyle)
269269
.withOutput(rowStructureStyle)
270270
.getRequestParameters();
271-
271+
272272
RESTServiceResultIterator iter = submitPlan(requestPlan, params, transaction);
273273
RowSetObject<T> rowset = new RowSetObject<>(rowFormat, datatypeStyle, rowStructureStyle, iter, rowHandle);
274274
rowset.init();
@@ -356,7 +356,31 @@ public <T> T columnInfoAs(PlanBuilder.PreparePlan plan, Class<T> as) {
356356
return handle.get();
357357
}
358358

359-
private <T extends AbstractReadHandle> String getRowFormat(T rowHandle) {
359+
@Override
360+
public <T extends JSONReadHandle> T graphql(JSONWriteHandle query, T resultsHandle) {
361+
if (resultsHandle == null) {
362+
throw new IllegalArgumentException("Must specify a handle for the results of the GraphQL query");
363+
}
364+
RequestParameters params = new RequestParameters();
365+
// Must force the MIME type before passing this to OkHttpServices - which does the exact same check below,
366+
// requiring that the query handle be an instance of HandleImplementation.
367+
HandleAccessor.checkHandle(query, "write").setMimetype("application/graphql");
368+
return services.postResource(requestLogger, "rows/graphql", null, params, query, resultsHandle);
369+
}
370+
371+
@Override
372+
public <T> T graphqlAs(JSONWriteHandle query, Class<T> as) {
373+
ContentHandle<T> handle = handleFor(as);
374+
if (!(handle instanceof JSONReadHandle)) {
375+
throw new IllegalArgumentException("The handle is not an instance of JSONReadHandle.");
376+
}
377+
if (graphql(query, (JSONReadHandle) handle) == null) {
378+
return null;
379+
}
380+
return handle.get();
381+
}
382+
383+
private <T extends AbstractReadHandle> String getRowFormat(T rowHandle) {
360384
if (rowHandle == null) {
361385
throw new IllegalArgumentException("Must specify a handle to iterate over the rows");
362386
}

marklogic-client-api/src/main/java/com/marklogic/client/row/RowManager.java

Lines changed: 50 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -53,29 +53,29 @@ public enum RowStructure{ARRAY, OBJECT}
5353
/**
5454
* Specifies whether to emit the data type of each column in each row or only in the header
5555
* in the response for requests made with the row manager.
56-
*
56+
*
5757
* The distinction is significant when getting the rows as JSON or XML.
5858
* Because the server supports columns with variant data types, the default is in each row.
5959
* You can configure the row manager to return more concise objects if the data type
6060
* is consistent or if you aren't using the data type.
61-
*
61+
*
6262
* @param style the part of the rowset that should contain data types
6363
*/
6464
void setDatatypeStyle(RowSetPart style);
6565

6666
/**
67-
* Returns whether each row should have an array or object structure
67+
* Returns whether each row should have an array or object structure
6868
* in the response for requests made with the row manager.
6969
* @return the style of row structure
7070
*/
7171
RowStructure getRowStructureStyle();
7272
/**
7373
* Specifies whether to get each row as an object (the default) or as an array
7474
* in the response for requests made with the row manager.
75-
*
75+
*
7676
* The distinction is significant when getting the rows as JSON
7777
* and also when executing a map or reduce function on the server.
78-
*
78+
*
7979
* @param style the structure of rows in the response
8080
*/
8181
void setRowStructureStyle(RowStructure style);
@@ -130,14 +130,14 @@ public enum RowStructure{ARRAY, OBJECT}
130130

131131
/**
132132
* Execute the given plan without returning any result.
133-
*
133+
*
134134
* @param plan the definition of a plan
135135
*/
136136
void execute(Plan plan);
137137

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

183183
/**
184184
* Constructs and retrieves a set of database rows based on a plan using
185-
* a JSON or XML handle for each row and reflecting documents written or
185+
* a JSON or XML handle for each row and reflecting documents written or
186186
* deleted by an uncommitted transaction.
187-
*
187+
*
188188
* The IO class must have been registered before creating the database client.
189-
* By default, the provided handles that implement
189+
* By default, the provided handles that implement
190190
* {@link com.marklogic.client.io.marker.ContentHandle ContentHandle} are registered.
191-
*
191+
*
192192
* <a href="../../../../overview-summary.html#ShortcutMethods">Learn more about shortcut methods</a>
193-
*
193+
*
194194
* @param plan the definition of a plan for the database rows
195195
* @param as the IO class for reading each row as JSON or XML content
196196
* @param <T> the type of object that will be returned by the handle registered for it
@@ -199,15 +199,15 @@ public enum RowStructure{ARRAY, OBJECT}
199199
<T> RowSet<T> resultRowsAs(Plan plan, Class<T> as);
200200
/**
201201
* Constructs and retrieves a set of database rows based on a plan using
202-
* a JSON or XML handle for each row and reflecting documents written or
202+
* a JSON or XML handle for each row and reflecting documents written or
203203
* deleted by an uncommitted transaction.
204-
*
204+
*
205205
* The IO class must have been registered before creating the database client.
206-
* By default, the provided handles that implement
206+
* By default, the provided handles that implement
207207
* {@link com.marklogic.client.io.marker.ContentHandle ContentHandle} are registered.
208-
*
208+
*
209209
* <a href="../../../../overview-summary.html#ShortcutMethods">Learn more about shortcut methods</a>
210-
*
210+
*
211211
* @param plan the definition of a plan for the database rows
212212
* @param as the IO class for reading each row as JSON or XML content
213213
* @param transaction a open transaction for documents from which rows have been projected
@@ -240,13 +240,13 @@ public enum RowStructure{ARRAY, OBJECT}
240240
/**
241241
* Constructs and retrieves a set of database rows based on a plan
242242
* in the representation specified by the IO class.
243-
*
243+
*
244244
* The IO class must have been registered before creating the database client.
245-
* By default, the provided handles that implement
245+
* By default, the provided handles that implement
246246
* {@link com.marklogic.client.io.marker.ContentHandle ContentHandle} are registered.
247-
*
247+
*
248248
* <a href="../../../../overview-summary.html#ShortcutMethods">Learn more about shortcut methods</a>
249-
*
249+
*
250250
* @param plan the definition of a plan for the database rows
251251
* @param as the IO class for reading the set of rows
252252
* @param <T> the type of the IO object for reading the set of rows
@@ -257,13 +257,13 @@ public enum RowStructure{ARRAY, OBJECT}
257257
* Constructs and retrieves a set of database rows based on a plan
258258
* in the representation specified by the IO class and reflecting
259259
* documents written or deleted by an uncommitted transaction.
260-
*
260+
*
261261
* The IO class must have been registered before creating the database client.
262-
* By default, the provided handles that implement
262+
* By default, the provided handles that implement
263263
* {@link com.marklogic.client.io.marker.ContentHandle ContentHandle} are registered.
264-
*
264+
*
265265
* <a href="../../../../overview-summary.html#ShortcutMethods">Learn more about shortcut methods</a>
266-
*
266+
*
267267
* @param plan the definition of a plan for the database rows
268268
* @param as the IO class for reading the set of rows
269269
* @param transaction a open transaction for documents from which rows have been projected
@@ -284,13 +284,13 @@ public enum RowStructure{ARRAY, OBJECT}
284284
/**
285285
* Constructs a plan for retrieving a set of database rows and returns an explanation
286286
* of the plan in the representation specified by the IO class.
287-
*
287+
*
288288
* The IO class must have been registered before creating the database client.
289-
* By default, the provided handles that implement
289+
* By default, the provided handles that implement
290290
* {@link com.marklogic.client.io.marker.ContentHandle ContentHandle} are registered.
291-
*
291+
*
292292
* <a href="../../../../overview-summary.html#ShortcutMethods">Learn more about shortcut methods</a>
293-
*
293+
*
294294
* @param plan the definition of a plan for database rows
295295
* @param as the IO class for reading the explanation for the plan
296296
* @param <T> the type of the IO object for reading the explanation
@@ -361,4 +361,24 @@ public enum RowStructure{ARRAY, OBJECT}
361361
* @return an object of the IO class with the content of the column information for the plan
362362
*/
363363
<T> T columnInfoAs(PlanBuilder.PreparePlan plan, Class<T> as);
364+
365+
/**
366+
* Executes a GraphQL query against the database and returns the results as a JSON object.
367+
*
368+
* @param query the GraphQL query to execute
369+
* @param resultsHandle the IO class for capturing the results
370+
* @param <T> the type of the IO object for r the results
371+
* @return an object of the IO class containing the query results, which will include error messages if the query fails
372+
*/
373+
<T extends JSONReadHandle> T graphql(JSONWriteHandle query, T resultsHandle);
374+
375+
/**
376+
* Executes a GraphQL query against the database and returns the results as a JSON object.
377+
*
378+
* @param query the GraphQL query to execute
379+
* @param as the class type of the results to return; typically JsonNode or String
380+
* @param <T> the type of the results to return
381+
* @return an instance of the given return type that contains the query results, which will include error messages if the query fails
382+
*/
383+
<T> T graphqlAs(JSONWriteHandle query, Class<T> as);
364384
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package com.marklogic.client.test.rows;
2+
3+
import com.fasterxml.jackson.databind.JsonNode;
4+
import com.fasterxml.jackson.databind.node.ObjectNode;
5+
import com.marklogic.client.io.JacksonHandle;
6+
import com.marklogic.client.io.StringHandle;
7+
import com.marklogic.client.row.RowManager;
8+
import com.marklogic.client.test.Common;
9+
import org.junit.jupiter.api.BeforeEach;
10+
import org.junit.jupiter.api.Test;
11+
12+
import static org.junit.jupiter.api.Assertions.assertEquals;
13+
import static org.junit.jupiter.api.Assertions.assertTrue;
14+
15+
public class GraphQLTest extends AbstractOpticUpdateTest {
16+
17+
private final static String QUERY = "query myQuery { opticUnitTest_musician {firstName lastName}}";
18+
19+
private RowManager rowManager;
20+
21+
@BeforeEach
22+
void beforeEach() {
23+
rowManager = Common.newClient().newRowManager();
24+
}
25+
26+
@Test
27+
void jsonQuery() {
28+
ObjectNode query = mapper.createObjectNode().put("query", QUERY);
29+
JsonNode response = rowManager.graphql(new JacksonHandle(query), new JacksonHandle()).get();
30+
verifyResponse(response);
31+
}
32+
33+
@Test
34+
void stringQuery() {
35+
JsonNode response = rowManager.graphql(new StringHandle("{\"query\": \"" + QUERY + "\"}"), new JacksonHandle()).get();
36+
verifyResponse(response);
37+
}
38+
39+
@Test
40+
void getResultAsJson() {
41+
ObjectNode query = mapper.createObjectNode().put("query", QUERY);
42+
JsonNode response = rowManager.graphqlAs(new JacksonHandle(query), JsonNode.class);
43+
verifyResponse(response);
44+
}
45+
46+
@Test
47+
void getResultAsString() throws Exception {
48+
ObjectNode query = mapper.createObjectNode().put("query", QUERY);
49+
String response = rowManager.graphqlAs(new JacksonHandle(query), String.class);
50+
verifyResponse(mapper.readTree(response));
51+
}
52+
53+
@Test
54+
void invalidQuery() {
55+
ObjectNode query = mapper.createObjectNode().put("query", "this is not valid");
56+
JsonNode response = rowManager.graphql(new JacksonHandle(query), new JacksonHandle()).get();
57+
58+
assertTrue(response.has("errors"));
59+
assertEquals(1, response.get("errors").size());
60+
61+
String message = response.get("errors").get(0).get("message").asText();
62+
assertTrue(message.startsWith("GRAPHQL-PARSE: Error parsing the GraphQL request string"),
63+
"Unexpected error message: " + message);
64+
}
65+
66+
private void verifyResponse(JsonNode response) {
67+
JsonNode data = response.get("data");
68+
JsonNode musicians = data.get("opticUnitTest_musician");
69+
assertEquals(4, musicians.size());
70+
musicians.forEach(musician -> {
71+
assertEquals(2, musician.size());
72+
assertTrue(musician.has("firstName"));
73+
assertTrue(musician.has("lastName"));
74+
});
75+
}
76+
}

0 commit comments

Comments
 (0)