Skip to content
This repository was archived by the owner on Sep 3, 2021. It is now read-only.

Commit 62db408

Browse files
Experimental mutation API: Fixes for data input objects (#536)
* Update cypherTest.test.js * Update augmentSchemaTest.test.js * generates _Create and _Update argument types instead of _Data * generated _Create and _Update input objects instead of _Data This preverse nonnullability when creating a node and keeps fields optional when updating * translates _Create and _Update input objects instead of _Data allows for generating @id value with apoc.create.uuid() when not providing a value, for the ON CREATE clause when using Merge * Update augmentSchemaTest.js * Update testSchema.js * Update translate.js * Update cypherTest.test.js
1 parent acedcb2 commit 62db408

File tree

7 files changed

+396
-60
lines changed

7 files changed

+396
-60
lines changed

src/augment/types/node/mutation.js

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ import {
3030
/**
3131
* An enum describing the names of node type mutations
3232
*/
33-
const NodeMutation = {
33+
export const NodeMutation = {
3434
CREATE: 'Create',
3535
UPDATE: 'Update',
3636
DELETE: 'Delete',
@@ -213,20 +213,29 @@ const buildNodeMutationObjectArguments = ({ typeName, operationName = '' }) => {
213213
}
214214
}
215215
};
216-
const propertyInputConfig = {
216+
const propertyCreateInputConfig = {
217217
name: 'data',
218218
type: {
219-
name: `_${typeName}Data`,
219+
name: `_${typeName}Create`,
220+
wrappers: {
221+
[TypeWrappers.NON_NULL_NAMED_TYPE]: true
222+
}
223+
}
224+
};
225+
const propertyUpdateInputConfig = {
226+
name: 'data',
227+
type: {
228+
name: `_${typeName}Update`,
220229
wrappers: {
221230
[TypeWrappers.NON_NULL_NAMED_TYPE]: true
222231
}
223232
}
224233
};
225234
if (operationName === NodeMutation.CREATE) {
226-
args.push(propertyInputConfig);
235+
args.push(propertyCreateInputConfig);
227236
} else if (operationName === NodeMutation.UPDATE) {
228237
args.push(nodeSelectionConfig);
229-
args.push(propertyInputConfig);
238+
args.push(propertyUpdateInputConfig);
230239
} else if (operationName === NodeMutation.MERGE) {
231240
const keySelectionInputConfig = {
232241
name: 'where',
@@ -238,7 +247,7 @@ const buildNodeMutationObjectArguments = ({ typeName, operationName = '' }) => {
238247
}
239248
};
240249
args.push(keySelectionInputConfig);
241-
args.push(propertyInputConfig);
250+
args.push(propertyCreateInputConfig);
242251
} else if (operationName === NodeMutation.DELETE) {
243252
args.push(nodeSelectionConfig);
244253
}

src/augment/types/node/selection.js

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import {
2828
buildPropertyFilters,
2929
buildLogicalFilterInputValues
3030
} from '../../input-values';
31+
import { NodeMutation } from './mutation';
3132

3233
/**
3334
* Gets a single field for use as a primary key
@@ -192,12 +193,19 @@ export const buildNodeSelectionInputTypes = ({
192193
definition,
193194
typeExtensionDefinitionMap
194195
});
195-
// Used by Create, Update, Merge
196-
generatedTypeMap = buildNodeDataInputObject({
196+
// Used by Create, Merge
197+
generatedTypeMap = buildNodeCreateInputObject({
197198
typeName,
198199
propertyInputValues,
199200
generatedTypeMap
200201
});
202+
// Used by Update
203+
generatedTypeMap = buildNodeUpdateInputObject({
204+
typeName,
205+
propertyInputValues,
206+
generatedTypeMap
207+
});
208+
201209
// Used by Update, Delete
202210
generatedTypeMap = buildNodeSelectionInputObject({
203211
typeName,
@@ -306,19 +314,47 @@ const buildNodeKeySelectionInputObject = ({
306314
return generatedTypeMap;
307315
};
308316

309-
const buildNodeDataInputObject = ({
317+
const buildNodeCreateInputObject = ({
318+
typeName,
319+
propertyInputValues = [],
320+
generatedTypeMap
321+
}) => {
322+
const propertyInputName = `_${typeName}Create`;
323+
const inputValues = propertyInputValues.map(field => {
324+
const { name, type, directives } = field;
325+
const isPrimaryKey = directives.some(
326+
directive => directive.name.value === 'id'
327+
);
328+
// keep nonnull and list type wrappers for Create and Merge node mutation,
329+
// expect for a primary key, so it could be generated if not provided
330+
if (isPrimaryKey) type.wrappers = {};
331+
return buildInputValue({
332+
name: buildName({ name }),
333+
type: buildNamedType(type)
334+
});
335+
});
336+
if (inputValues.length) {
337+
generatedTypeMap[propertyInputName] = buildInputObjectType({
338+
name: buildName({ name: propertyInputName }),
339+
fields: inputValues
340+
});
341+
}
342+
return generatedTypeMap;
343+
};
344+
345+
const buildNodeUpdateInputObject = ({
310346
typeName,
311347
propertyInputValues = [],
312348
generatedTypeMap
313349
}) => {
314-
const propertyInputName = `_${typeName}Data`;
350+
const propertyInputName = `_${typeName}Update`;
315351
const inputValues = propertyInputValues.map(field => {
316352
const { name, type } = field;
353+
// set all fields to optional, keep list type wrappers
354+
type.wrappers[TypeWrappers.NON_NULL_NAMED_TYPE] = false;
317355
return buildInputValue({
318356
name: buildName({ name }),
319-
type: buildNamedType({
320-
name: type.name
321-
})
357+
type: buildNamedType(type)
322358
});
323359
});
324360
if (inputValues.length) {

src/translate.js

Lines changed: 65 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,11 @@ import {
7373
isListTypeField,
7474
TypeWrappers
7575
} from './augment/fields';
76+
import {
77+
isPrimaryKeyField,
78+
isUniqueField,
79+
isIndexedField
80+
} from './augment/directives';
7681
import {
7782
analyzeMutationArguments,
7883
isNeo4jTypeArgument,
@@ -1740,14 +1745,24 @@ const nodeMergeOrUpdate = ({
17401745
// config.experimental
17411746
// no need to use .params key in this argument design
17421747
params = params.params;
1743-
const inputTranslation = translateNodeInputArgument({
1744-
dataArgument,
1745-
variableName,
1746-
params,
1747-
typeMap,
1748-
resolveInfo,
1749-
context
1750-
});
1748+
const [propertyStatements, generatePrimaryKey] = translateNodeInputArgument(
1749+
{
1750+
selectionArgument,
1751+
dataArgument,
1752+
params,
1753+
primaryKey,
1754+
typeMap,
1755+
fieldMap,
1756+
resolveInfo,
1757+
context
1758+
}
1759+
);
1760+
let onMatchStatements = ``;
1761+
if (propertyStatements.length > 0) {
1762+
onMatchStatements = `SET ${safeVar(
1763+
variableName
1764+
)} += {${propertyStatements.join(',')}} `;
1765+
}
17511766
if (isMergeMutation(resolveInfo)) {
17521767
const unwrappedType = unwrapNamedType({ type: selectionArgument.type });
17531768
const name = unwrappedType.name;
@@ -1761,9 +1776,20 @@ const nodeMergeOrUpdate = ({
17611776
resolveInfo,
17621777
cypherParams: getCypherParams(context)
17631778
});
1764-
query = `${cypherOperation} (${safeVariableName}:${safeLabelName}{${selectionExpression.join(
1765-
','
1766-
)}})${inputTranslation}\n`;
1779+
// generatePrimaryKey is either empty or contains a call to apoc.create.uuid for @id key
1780+
const onCreateProps = [...propertyStatements, ...generatePrimaryKey];
1781+
let onCreateStatements = ``;
1782+
if (onCreateProps.length > 0) {
1783+
onCreateStatements = `SET ${safeVar(
1784+
variableName
1785+
)} += {${onCreateProps.join(',')}}`;
1786+
}
1787+
const keySelectionStatement = selectionExpression.join(',');
1788+
query = `${cypherOperation} (${safeVariableName}:${safeLabelName}{${keySelectionStatement}})
1789+
ON CREATE
1790+
${onCreateStatements}
1791+
ON MATCH
1792+
${onMatchStatements}`;
17671793
} else {
17681794
const [predicate, serializedFilter] = translateNodeSelectionArgument({
17691795
variableName,
@@ -1772,7 +1798,8 @@ const nodeMergeOrUpdate = ({
17721798
schemaType,
17731799
resolveInfo
17741800
});
1775-
query = `${cypherOperation} (${safeVariableName}:${safeLabelName})${predicate}${inputTranslation}\n`;
1801+
query = `${cypherOperation} (${safeVariableName}:${safeLabelName})${predicate}
1802+
${onMatchStatements}\n`;
17761803
params = { ...params, ...serializedFilter };
17771804
}
17781805
} else {
@@ -1858,9 +1885,10 @@ RETURN ${safeVariableName}`;
18581885
};
18591886

18601887
const translateNodeInputArgument = ({
1888+
selectionArgument = {},
18611889
dataArgument = {},
1862-
variableName,
18631890
params,
1891+
primaryKey,
18641892
typeMap,
18651893
resolveInfo,
18661894
context
@@ -1870,20 +1898,37 @@ const translateNodeInputArgument = ({
18701898
const inputType = typeMap[name];
18711899
const inputValues = inputType.getFields();
18721900
const updateArgs = Object.values(inputValues).map(arg => arg.astNode);
1873-
let translation = '';
1874-
const paramUpdateStatements = buildCypherParameters({
1901+
let propertyStatements = buildCypherParameters({
18751902
args: updateArgs,
18761903
params,
18771904
paramKey: 'data',
18781905
resolveInfo,
18791906
cypherParams: getCypherParams(context)
18801907
});
1881-
if (paramUpdateStatements.length > 0) {
1882-
translation = `\nSET ${safeVar(
1883-
variableName
1884-
)} += {${paramUpdateStatements.join(',')}} `;
1908+
let primaryKeyStatement = [];
1909+
if (isMergeMutation(resolveInfo)) {
1910+
const unwrappedType = unwrapNamedType({ type: selectionArgument.type });
1911+
const name = unwrappedType.name;
1912+
const inputType = typeMap[name];
1913+
const inputValues = inputType.getFields();
1914+
const selectionArgs = Object.values(inputValues).map(arg => arg.astNode);
1915+
// check key selection values for @id key argument
1916+
const primaryKeySelectionValue = setPrimaryKeyValue({
1917+
args: selectionArgs,
1918+
params: params['where'],
1919+
primaryKey
1920+
});
1921+
const primaryKeyValue = setPrimaryKeyValue({
1922+
args: updateArgs,
1923+
params: params['data'],
1924+
primaryKey
1925+
});
1926+
if (primaryKeySelectionValue.length && primaryKeyValue.length) {
1927+
// apoc.create.uuid() statement returned for both, so a value exists in neither
1928+
primaryKeyStatement = primaryKeySelectionValue;
1929+
}
18851930
}
1886-
return translation;
1931+
return [propertyStatements, primaryKeyStatement];
18871932
};
18881933

18891934
const translateNodeSelectionArgument = ({

test/helpers/experimental/augmentSchemaTest.js

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -33,16 +33,38 @@ export function cypherTestRunner(
3333
testSchema +
3434
`
3535
type Mutation {
36-
CreateUser(data: _UserData!): User @hasScope(scopes: ["User: Create", "create:user"])
37-
UpdateUser(where: _UserWhere!, data: _UserData!): User @hasScope(scopes: ["User: Update", "update:user"])
36+
CreateUser(data: _UserCreate!): User @hasScope(scopes: ["User: Create", "create:user"])
37+
UpdateUser(where: _UserWhere!, data: _UserUpdate!): User @hasScope(scopes: ["User: Update", "update:user"])
3838
DeleteUser(where: _UserWhere!): User @hasScope(scopes: ["User: Delete", "delete:user"])
39-
MergeUser(where: _UserWhere!, data: _UserData!): User @hasScope(scopes: ["User: Merge", "merge:user"])
39+
MergeUser(where: _UserKeys!, data: _UserCreate!): User @hasScope(scopes: ["User: Merge", "merge:user"])
4040
}
4141
4242
type Query {
4343
User: [User] @hasScope(scopes: ["User: Read", "read:user"])
4444
}
4545
46+
input _UserCreate {
47+
idField: ID
48+
name: String
49+
names: [String]
50+
birthday: _Neo4jDateTimeInput
51+
birthdays: [_Neo4jDateTimeInput]
52+
uniqueString: String!
53+
indexedInt: Int
54+
extensionString: String!
55+
}
56+
57+
input _UserUpdate {
58+
idField: ID
59+
name: String
60+
names: [String]
61+
birthday: _Neo4jDateTimeInput
62+
birthdays: [_Neo4jDateTimeInput]
63+
uniqueString: String
64+
indexedInt: Int
65+
extensionString: String
66+
}
67+
4668
input _UserWhere {
4769
AND: [_UserWhere!]
4870
OR: [_UserWhere!]
@@ -81,15 +103,6 @@ export function cypherTestRunner(
81103
uniqueString: String
82104
indexedInt: Int
83105
}
84-
85-
input _UserData {
86-
idField: ID
87-
name: String
88-
birthday: _Neo4jDateTimeInput
89-
uniqueString: String
90-
indexedInt: Int
91-
extensionString: String
92-
}
93106
94107
`;
95108

test/helpers/experimental/testSchema.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ export const testSchema = `
44
type User {
55
idField: ID! @id
66
name: String
7+
names: [String]
78
birthday: DateTime
9+
birthdays: [DateTime]
810
uniqueString: String! @unique
911
indexedInt: Int @index
1012
liked: [Movie!]! @relation(

0 commit comments

Comments
 (0)