Skip to content

Commit 0a8122d

Browse files
authored
Added sorting argument for queries (aws#105)
The current graphQL utility does not support the sort argument in queries. Added support for sort argument in queries including nested. Wrote unit tests and fixed previous unit tests to follow added support.
1 parent 7e19348 commit 0a8122d

18 files changed

+1018
-193
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ This release contains new support for Apollo Server integration.
4040
* Support output of zip package of Apollo Server artifacts (([#70](https://github.com/aws/amazon-neptune-for-graphql/pull/70)), ([#72](https://github.com/aws/amazon-neptune-for-graphql/pull/72)), ([#73](https://github.com/aws/amazon-neptune-for-graphql/pull/73)), ([#75](https://github.com/aws/amazon-neptune-for-graphql/pull/75)), ([#76](https://github.com/aws/amazon-neptune-for-graphql/pull/76)))
4141
* Allow filtering using string comparison operators `eq`, `contains`, `startsWith`, `endsWith` (([#100](https://github.com/aws/amazon-neptune-for-graphql/pull/100))
4242
* Added pagination support through the addition of an `offset` argument in query options which can be used in combination with the existing `limit` (([#102](https://github.com/aws/amazon-neptune-for-graphql/pull/102))
43+
* Added support for queries with sorting ([#105](https://github.com/aws/amazon-neptune-for-graphql/pull/105))
4344

4445

4546
### Improvements

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -312,8 +312,9 @@ When using custom scalars in your schema (specified via `--input-schema-file`),
312312
- Schemas specified by `--input-schema-file` with `--create-update-aws-pipeline` may not contain custom scalars. See [AWS App Sync Scalar types in GraphQL](https://docs.aws.amazon.com/appsync/latest/devguide/scalars.html) for more information.
313313
- Schemas specified by `--input-schema-file` with `--create-update-apollo-server` or `--create-update-apollo-server-subgraph` which contain custom scalars require manual steps to add custom scalar resolvers for additional query validation.
314314
- Query fragments are supported for Apollo Server but not yet for App Sync
315-
- Inline fragments are not yet supported
316-
<br>
315+
- Inline fragments are not yet supported
316+
- When working with sort values as variables, Apollo Server may not support an array variable but will accept variables inside an array. For example ```sort: $test``` with variable ```{"test": [{"country": "ASC"}]}``` may produce an error but can be modified to ```sort: [$test]``` with variable ```{"test": {"country": "ASC"}}```.
317+
<br>
317318

318319
# Roadmap
319320
- Gremlin resolver.

src/graphdb.js

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -125,12 +125,18 @@ function formatProperties(properties = [], typeOverrides = new Map()) {
125125
}).join('');
126126
}
127127

128-
function graphDBInferenceSchema (graphbSchema, addMutations) {
128+
function graphDBInferenceSchema (graphdbSchema, addMutations) {
129129
let r = '';
130-
const gdbs = JSON.parse(graphbSchema);
130+
const gdbs = JSON.parse(graphdbSchema);
131131

132132
checkForDuplicateNames(gdbs);
133133

134+
// sorting direction enum
135+
r += `enum SortingDirection {\n`;
136+
r += '\tASC\n';
137+
r += '\tDESC\n';
138+
r += '}\n\n';
139+
134140
gdbs.nodeStructures.forEach(node => {
135141
// node type
136142
let nodeCase = cleanseLabel(node.label);
@@ -157,8 +163,8 @@ function graphDBInferenceSchema (graphbSchema, addMutations) {
157163

158164
if (direction.from === node.label && direction.to === node.label){
159165
if (direction.relationship === 'MANY-MANY') {
160-
r += `\t${nodeCase.toLocaleLowerCase() + edgeCase}sOut(filter: ${nodeCase}Input, options: Options): [${nodeCase}] @relationship(edgeType:"${edge.label}", direction:OUT)\n`;
161-
r += `\t${nodeCase.toLocaleLowerCase() + edgeCase}sIn(filter: ${nodeCase}Input, options: Options): [${nodeCase}] @relationship(edgeType:"${edge.label}", direction:IN)\n`;
166+
r += `\t${nodeCase.toLocaleLowerCase() + edgeCase}sOut(filter: ${nodeCase}Input, options: Options, sort: [${nodeCase}Sort!]): [${nodeCase}] @relationship(edgeType:"${edge.label}", direction:OUT)\n`;
167+
r += `\t${nodeCase.toLocaleLowerCase() + edgeCase}sIn(filter: ${nodeCase}Input, options: Options, sort: [${nodeCase}Sort!]): [${nodeCase}] @relationship(edgeType:"${edge.label}", direction:IN)\n`;
162168
}
163169
if (direction.relationship === 'ONE-ONE') {
164170
r += `\t${nodeCase.toLocaleLowerCase() + edgeCase}Out: ${nodeCase} @relationship(edgeType:"${edge.label}", direction:OUT)\n`;
@@ -170,10 +176,10 @@ function graphDBInferenceSchema (graphbSchema, addMutations) {
170176

171177
if (direction.from === node.label && direction.to !== node.label){
172178
if (direction.relationship === 'MANY-MANY') {
173-
r += `\t${toCase.toLocaleLowerCase() + edgeCase}sOut(filter: ${toCase}Input, options: Options): [${toCase}] @relationship(edgeType:"${edge.label}", direction:OUT)\n`;
179+
r += `\t${toCase.toLocaleLowerCase() + edgeCase}sOut(filter: ${toCase}Input, options: Options, sort: [${toCase}Sort!]): [${toCase}] @relationship(edgeType:"${edge.label}", direction:OUT)\n`;
174180
}
175181
if (direction.relationship === 'ONE-MANY') {
176-
r += `\t${toCase.toLocaleLowerCase() + edgeCase}sOut(filter: ${toCase}Input, options: Options): [${toCase}] @relationship(edgeType:"${edge.label}", direction:OUT)\n`;
182+
r += `\t${toCase.toLocaleLowerCase() + edgeCase}sOut(filter: ${toCase}Input, options: Options, sort: [${toCase}Sort!]): [${toCase}] @relationship(edgeType:"${edge.label}", direction:OUT)\n`;
177183
}
178184
if (direction.relationship === 'MANY-ONE') {
179185
r += `\t${toCase.toLocaleLowerCase() + edgeCase}Out: ${toCase} @relationship(edgeType:"${edge.label}", direction:OUT)\n`;
@@ -184,13 +190,13 @@ function graphDBInferenceSchema (graphbSchema, addMutations) {
184190

185191
if (direction.from !== node.label && direction.to === node.label){
186192
if (direction.relationship === 'MANY-MANY') {
187-
r += `\t${fromCase.toLocaleLowerCase() + edgeCase}sIn(filter: ${fromCase}Input, options: Options): [${fromCase}] @relationship(edgeType:"${edge.label}", direction:IN)\n`
193+
r += `\t${fromCase.toLocaleLowerCase() + edgeCase}sIn(filter: ${fromCase}Input, options: Options, sort: [${fromCase}Sort!]): [${fromCase}] @relationship(edgeType:"${edge.label}", direction:IN)\n`
188194
}
189195
if (direction.relationship === 'ONE-MANY') {
190196
r += `\t${fromCase.toLocaleLowerCase() + edgeCase}In: ${fromCase} @relationship(edgeType:"${edge.label}", direction:IN)\n`;
191197
}
192198
if (direction.relationship === 'MANY-ONE') {
193-
r += `\t${fromCase.toLocaleLowerCase() + edgeCase}sIn(filter: ${fromCase}Input, options: Options): [${fromCase}] @relationship(edgeType:"${edge.label}", direction:IN)\n`;
199+
r += `\t${fromCase.toLocaleLowerCase() + edgeCase}sIn(filter: ${fromCase}Input, options: Options, sort: [${fromCase}Sort!]): [${fromCase}] @relationship(edgeType:"${edge.label}", direction:IN)\n`;
194200
}
195201
if (!edgeTypes.includes(edge.label))
196202
edgeTypes.push(edge.label);
@@ -231,6 +237,15 @@ function graphDBInferenceSchema (graphbSchema, addMutations) {
231237
r += formatProperties(nodeProperties, new Map([['ID', 'ID! @id']]));
232238
r += '}\n\n';
233239
}
240+
241+
// sort input
242+
const sortProperties = nodeProperties.map(prop => ({
243+
...prop,
244+
type: 'SortingDirection'
245+
}));
246+
r += `input ${nodeCase}Sort {\n`;
247+
r += formatProperties(sortProperties);
248+
r += '}\n\n';
234249
})
235250

236251
const nodeLabels = new Set(gdbs.nodeStructures.map((n) => n.label));
@@ -305,7 +320,7 @@ function graphDBInferenceSchema (graphbSchema, addMutations) {
305320
gdbs.nodeStructures.forEach(node => {
306321
let nodeCase = toPascalCase(cleanseLabel(node.label));
307322
r += `\tgetNode${nodeCase}(filter: ${nodeCase}Input): ${nodeCase}\n`;
308-
r += `\tgetNode${nodeCase}s(filter: ${nodeCase}Input, options: Options): [${nodeCase}]\n`;
323+
r += `\tgetNode${nodeCase}s(filter: ${nodeCase}Input, options: Options, sort: [${nodeCase}Sort!]): [${nodeCase}]\n`;
309324
});
310325
r += '}\n\n';
311326

src/schemaModelValidator.js

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,8 @@ function addNode(def) {
142142
const idField = getIdFieldWithDirective(def);
143143

144144
// General Input type
145-
typesToAdd.push(`input ${name}Input {\n${print(getInputFields(def))}\n}`);
145+
const scalarFields = getInputFields(def);
146+
typesToAdd.push(`input ${name}Input {\n${print(scalarFields)}\n}`);
146147

147148
// Create Input type
148149
const createFields = [];
@@ -178,9 +179,20 @@ function addNode(def) {
178179
}
179180
typesToAdd.push(`input ${name}UpdateInput {\n${print(updateFields)}\n}`);
180181

182+
// Sort Input type
183+
const sortFields = scalarFields.map(scalarField => ({
184+
...scalarField,
185+
type: {
186+
kind: 'NamedType',
187+
name: {kind: 'Name', value: 'SortDirection'}
188+
},
189+
directives: []
190+
}));
191+
typesToAdd.push(`input ${name}Sort {\n${print(sortFields)}\n}`);
192+
181193
// Create query
182194
queriesToAdd.push(`getNode${name}(filter: ${name}Input): ${name}\n`);
183-
queriesToAdd.push(`getNode${name}s(filter: ${name}Input, options: Options): [${name}]\n`);
195+
queriesToAdd.push(`getNode${name}s(filter: ${name}Input, options: Options, sort: [${name}Sort!]): [${name}]\n`);
184196

185197
// Create mutation
186198
mutationsToAdd.push(`createNode${name}(input: ${name}CreateInput!): ${name}\n`);
@@ -215,7 +227,7 @@ function addEdge(from, to, edgeName) {
215227
}
216228

217229

218-
function addFilterOptionsArguments(field) {
230+
function addFilterOptionsSortArguments(field) {
219231
// filter
220232
field.arguments.push({
221233
kind: 'InputValueDefinition',
@@ -247,6 +259,22 @@ function addFilterOptionsArguments(field) {
247259
}
248260
}
249261
});
262+
263+
// sort
264+
field.arguments.push({
265+
kind: 'InputValueDefinition',
266+
name: {
267+
kind: 'Name',
268+
value: 'sort'
269+
},
270+
type: {
271+
kind: 'NamedType',
272+
name: {
273+
kind: 'Name',
274+
value: field.type.type.name.value + 'Sort'
275+
}
276+
}
277+
});
250278
}
251279

252280

@@ -306,6 +334,9 @@ function inferGraphDatabaseDirectives(schemaModel) {
306334
.filter(definition => definition.kind === 'EnumTypeDefinition')
307335
.forEach(definition => enumTypes.push(definition.name.value));
308336

337+
// Generate sort enum
338+
typesToAdd.push(`enum SortDirection {\nASC\nDESC\n}`);
339+
309340
schemaModel.definitions
310341
.filter(definition => definition.kind === 'ScalarTypeDefinition')
311342
.forEach(definition => customScalarTypes.push(definition.name.value));
@@ -338,7 +369,7 @@ function inferGraphDatabaseDirectives(schemaModel) {
338369
{
339370
try {
340371
if (field.type.kind === 'ListType')
341-
addFilterOptionsArguments(field);
372+
addFilterOptionsSortArguments(field);
342373
}
343374
catch {}
344375

src/test/airports-mutations.graphql

Lines changed: 52 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
1+
enum SortingDirection {
2+
ASC
3+
DESC
4+
}
5+
16
type Continent @alias(property:"continent") {
27
_id: ID! @id
38
type: String
49
code: String
510
desc: String
6-
airportContainssOut(filter: AirportInput, options: Options): [Airport] @relationship(edgeType:"contains", direction:OUT)
11+
airportContainssOut(filter: AirportInput, options: Options, sort: [AirportSort!]): [Airport] @relationship(edgeType:"contains", direction:OUT)
712
contains:Contains
813
}
914

@@ -28,12 +33,19 @@ input ContinentUpdateInput {
2833
desc: String
2934
}
3035

36+
input ContinentSort {
37+
_id: SortingDirection
38+
type: SortingDirection
39+
code: SortingDirection
40+
desc: SortingDirection
41+
}
42+
3143
type Country @alias(property:"country") {
3244
_id: ID! @id
3345
type: String
3446
code: String
3547
desc: String
36-
airportContainssOut(filter: AirportInput, options: Options): [Airport] @relationship(edgeType:"contains", direction:OUT)
48+
airportContainssOut(filter: AirportInput, options: Options, sort: [AirportSort!]): [Airport] @relationship(edgeType:"contains", direction:OUT)
3749
contains:Contains
3850
}
3951

@@ -58,6 +70,13 @@ input CountryUpdateInput {
5870
desc: String
5971
}
6072

73+
input CountrySort {
74+
_id: SortingDirection
75+
type: SortingDirection
76+
code: SortingDirection
77+
desc: SortingDirection
78+
}
79+
6180
type Version @alias(property:"version") {
6281
_id: ID! @id
6382
date: String
@@ -94,6 +113,15 @@ input VersionUpdateInput {
94113
code: String
95114
}
96115

116+
input VersionSort {
117+
_id: SortingDirection
118+
date: SortingDirection
119+
desc: SortingDirection
120+
author: SortingDirection
121+
type: SortingDirection
122+
code: SortingDirection
123+
}
124+
97125
type Airport @alias(property:"airport") {
98126
_id: ID! @id
99127
type: String
@@ -110,8 +138,8 @@ type Airport @alias(property:"airport") {
110138
elev: Int
111139
continentContainsIn: Continent @relationship(edgeType:"contains", direction:IN)
112140
countryContainsIn: Country @relationship(edgeType:"contains", direction:IN)
113-
airportRoutesOut(filter: AirportInput, options: Options): [Airport] @relationship(edgeType:"route", direction:OUT)
114-
airportRoutesIn(filter: AirportInput, options: Options): [Airport] @relationship(edgeType:"route", direction:IN)
141+
airportRoutesOut(filter: AirportInput, options: Options, sort: [AirportSort!]): [Airport] @relationship(edgeType:"route", direction:OUT)
142+
airportRoutesIn(filter: AirportInput, options: Options, sort: [AirportSort!]): [Airport] @relationship(edgeType:"route", direction:IN)
115143
contains:Contains
116144
route:Route
117145
}
@@ -164,6 +192,22 @@ input AirportUpdateInput {
164192
elev: Int
165193
}
166194

195+
input AirportSort {
196+
_id: SortingDirection
197+
type: SortingDirection
198+
city: SortingDirection
199+
icao: SortingDirection
200+
code: SortingDirection
201+
country: SortingDirection
202+
lat: SortingDirection
203+
longest: SortingDirection
204+
runways: SortingDirection
205+
desc: SortingDirection
206+
lon: SortingDirection
207+
region: SortingDirection
208+
elev: SortingDirection
209+
}
210+
167211
type Contains @alias(property:"contains") {
168212
_id: ID! @id
169213
}
@@ -191,13 +235,13 @@ input StringScalarFilters {
191235

192236
type Query {
193237
getNodeContinent(filter: ContinentInput): Continent
194-
getNodeContinents(filter: ContinentInput, options: Options): [Continent]
238+
getNodeContinents(filter: ContinentInput, options: Options, sort: [ContinentSort!]): [Continent]
195239
getNodeCountry(filter: CountryInput): Country
196-
getNodeCountrys(filter: CountryInput, options: Options): [Country]
240+
getNodeCountrys(filter: CountryInput, options: Options, sort: [CountrySort!]): [Country]
197241
getNodeVersion(filter: VersionInput): Version
198-
getNodeVersions(filter: VersionInput, options: Options): [Version]
242+
getNodeVersions(filter: VersionInput, options: Options, sort: [VersionSort!]): [Version]
199243
getNodeAirport(filter: AirportInput): Airport
200-
getNodeAirports(filter: AirportInput, options: Options): [Airport]
244+
getNodeAirports(filter: AirportInput, options: Options, sort: [AirportSort!]): [Airport]
201245
}
202246

203247
type Mutation {

0 commit comments

Comments
 (0)