diff --git a/src/augment/types/node/mutation.js b/src/augment/types/node/mutation.js index 0746a19f..3994db81 100644 --- a/src/augment/types/node/mutation.js +++ b/src/augment/types/node/mutation.js @@ -30,7 +30,7 @@ import { /** * An enum describing the names of node type mutations */ -const NodeMutation = { +export const NodeMutation = { CREATE: 'Create', UPDATE: 'Update', DELETE: 'Delete', @@ -213,20 +213,29 @@ const buildNodeMutationObjectArguments = ({ typeName, operationName = '' }) => { } } }; - const propertyInputConfig = { + const propertyCreateInputConfig = { name: 'data', type: { - name: `_${typeName}Data`, + name: `_${typeName}Create`, + wrappers: { + [TypeWrappers.NON_NULL_NAMED_TYPE]: true + } + } + }; + const propertyUpdateInputConfig = { + name: 'data', + type: { + name: `_${typeName}Update`, wrappers: { [TypeWrappers.NON_NULL_NAMED_TYPE]: true } } }; if (operationName === NodeMutation.CREATE) { - args.push(propertyInputConfig); + args.push(propertyCreateInputConfig); } else if (operationName === NodeMutation.UPDATE) { args.push(nodeSelectionConfig); - args.push(propertyInputConfig); + args.push(propertyUpdateInputConfig); } else if (operationName === NodeMutation.MERGE) { const keySelectionInputConfig = { name: 'where', @@ -238,7 +247,7 @@ const buildNodeMutationObjectArguments = ({ typeName, operationName = '' }) => { } }; args.push(keySelectionInputConfig); - args.push(propertyInputConfig); + args.push(propertyCreateInputConfig); } else if (operationName === NodeMutation.DELETE) { args.push(nodeSelectionConfig); } diff --git a/src/augment/types/node/selection.js b/src/augment/types/node/selection.js index c7734524..f9fbc3e7 100644 --- a/src/augment/types/node/selection.js +++ b/src/augment/types/node/selection.js @@ -28,6 +28,7 @@ import { buildPropertyFilters, buildLogicalFilterInputValues } from '../../input-values'; +import { NodeMutation } from './mutation'; /** * Gets a single field for use as a primary key @@ -192,12 +193,19 @@ export const buildNodeSelectionInputTypes = ({ definition, typeExtensionDefinitionMap }); - // Used by Create, Update, Merge - generatedTypeMap = buildNodeDataInputObject({ + // Used by Create, Merge + generatedTypeMap = buildNodeCreateInputObject({ typeName, propertyInputValues, generatedTypeMap }); + // Used by Update + generatedTypeMap = buildNodeUpdateInputObject({ + typeName, + propertyInputValues, + generatedTypeMap + }); + // Used by Update, Delete generatedTypeMap = buildNodeSelectionInputObject({ typeName, @@ -306,19 +314,47 @@ const buildNodeKeySelectionInputObject = ({ return generatedTypeMap; }; -const buildNodeDataInputObject = ({ +const buildNodeCreateInputObject = ({ + typeName, + propertyInputValues = [], + generatedTypeMap +}) => { + const propertyInputName = `_${typeName}Create`; + const inputValues = propertyInputValues.map(field => { + const { name, type, directives } = field; + const isPrimaryKey = directives.some( + directive => directive.name.value === 'id' + ); + // keep nonnull and list type wrappers for Create and Merge node mutation, + // expect for a primary key, so it could be generated if not provided + if (isPrimaryKey) type.wrappers = {}; + return buildInputValue({ + name: buildName({ name }), + type: buildNamedType(type) + }); + }); + if (inputValues.length) { + generatedTypeMap[propertyInputName] = buildInputObjectType({ + name: buildName({ name: propertyInputName }), + fields: inputValues + }); + } + return generatedTypeMap; +}; + +const buildNodeUpdateInputObject = ({ typeName, propertyInputValues = [], generatedTypeMap }) => { - const propertyInputName = `_${typeName}Data`; + const propertyInputName = `_${typeName}Update`; const inputValues = propertyInputValues.map(field => { const { name, type } = field; + // set all fields to optional, keep list type wrappers + type.wrappers[TypeWrappers.NON_NULL_NAMED_TYPE] = false; return buildInputValue({ name: buildName({ name }), - type: buildNamedType({ - name: type.name - }) + type: buildNamedType(type) }); }); if (inputValues.length) { diff --git a/src/translate.js b/src/translate.js index 51de0d2f..a926b94b 100644 --- a/src/translate.js +++ b/src/translate.js @@ -73,6 +73,11 @@ import { isListTypeField, TypeWrappers } from './augment/fields'; +import { + isPrimaryKeyField, + isUniqueField, + isIndexedField +} from './augment/directives'; import { analyzeMutationArguments, isNeo4jTypeArgument, @@ -1740,14 +1745,24 @@ const nodeMergeOrUpdate = ({ // config.experimental // no need to use .params key in this argument design params = params.params; - const inputTranslation = translateNodeInputArgument({ - dataArgument, - variableName, - params, - typeMap, - resolveInfo, - context - }); + const [propertyStatements, generatePrimaryKey] = translateNodeInputArgument( + { + selectionArgument, + dataArgument, + params, + primaryKey, + typeMap, + fieldMap, + resolveInfo, + context + } + ); + let onMatchStatements = ``; + if (propertyStatements.length > 0) { + onMatchStatements = `SET ${safeVar( + variableName + )} += {${propertyStatements.join(',')}} `; + } if (isMergeMutation(resolveInfo)) { const unwrappedType = unwrapNamedType({ type: selectionArgument.type }); const name = unwrappedType.name; @@ -1761,9 +1776,20 @@ const nodeMergeOrUpdate = ({ resolveInfo, cypherParams: getCypherParams(context) }); - query = `${cypherOperation} (${safeVariableName}:${safeLabelName}{${selectionExpression.join( - ',' - )}})${inputTranslation}\n`; + // generatePrimaryKey is either empty or contains a call to apoc.create.uuid for @id key + const onCreateProps = [...propertyStatements, ...generatePrimaryKey]; + let onCreateStatements = ``; + if (onCreateProps.length > 0) { + onCreateStatements = `SET ${safeVar( + variableName + )} += {${onCreateProps.join(',')}}`; + } + const keySelectionStatement = selectionExpression.join(','); + query = `${cypherOperation} (${safeVariableName}:${safeLabelName}{${keySelectionStatement}}) +ON CREATE + ${onCreateStatements} +ON MATCH + ${onMatchStatements}`; } else { const [predicate, serializedFilter] = translateNodeSelectionArgument({ variableName, @@ -1772,7 +1798,8 @@ const nodeMergeOrUpdate = ({ schemaType, resolveInfo }); - query = `${cypherOperation} (${safeVariableName}:${safeLabelName})${predicate}${inputTranslation}\n`; + query = `${cypherOperation} (${safeVariableName}:${safeLabelName})${predicate} +${onMatchStatements}\n`; params = { ...params, ...serializedFilter }; } } else { @@ -1858,9 +1885,10 @@ RETURN ${safeVariableName}`; }; const translateNodeInputArgument = ({ + selectionArgument = {}, dataArgument = {}, - variableName, params, + primaryKey, typeMap, resolveInfo, context @@ -1870,20 +1898,37 @@ const translateNodeInputArgument = ({ const inputType = typeMap[name]; const inputValues = inputType.getFields(); const updateArgs = Object.values(inputValues).map(arg => arg.astNode); - let translation = ''; - const paramUpdateStatements = buildCypherParameters({ + let propertyStatements = buildCypherParameters({ args: updateArgs, params, paramKey: 'data', resolveInfo, cypherParams: getCypherParams(context) }); - if (paramUpdateStatements.length > 0) { - translation = `\nSET ${safeVar( - variableName - )} += {${paramUpdateStatements.join(',')}} `; + let primaryKeyStatement = []; + if (isMergeMutation(resolveInfo)) { + const unwrappedType = unwrapNamedType({ type: selectionArgument.type }); + const name = unwrappedType.name; + const inputType = typeMap[name]; + const inputValues = inputType.getFields(); + const selectionArgs = Object.values(inputValues).map(arg => arg.astNode); + // check key selection values for @id key argument + const primaryKeySelectionValue = setPrimaryKeyValue({ + args: selectionArgs, + params: params['where'], + primaryKey + }); + const primaryKeyValue = setPrimaryKeyValue({ + args: updateArgs, + params: params['data'], + primaryKey + }); + if (primaryKeySelectionValue.length && primaryKeyValue.length) { + // apoc.create.uuid() statement returned for both, so a value exists in neither + primaryKeyStatement = primaryKeySelectionValue; + } } - return translation; + return [propertyStatements, primaryKeyStatement]; }; const translateNodeSelectionArgument = ({ diff --git a/test/helpers/experimental/augmentSchemaTest.js b/test/helpers/experimental/augmentSchemaTest.js index 3b12cba7..92a6424a 100644 --- a/test/helpers/experimental/augmentSchemaTest.js +++ b/test/helpers/experimental/augmentSchemaTest.js @@ -33,16 +33,38 @@ export function cypherTestRunner( testSchema + ` type Mutation { - CreateUser(data: _UserData!): User @hasScope(scopes: ["User: Create", "create:user"]) - UpdateUser(where: _UserWhere!, data: _UserData!): User @hasScope(scopes: ["User: Update", "update:user"]) + CreateUser(data: _UserCreate!): User @hasScope(scopes: ["User: Create", "create:user"]) + UpdateUser(where: _UserWhere!, data: _UserUpdate!): User @hasScope(scopes: ["User: Update", "update:user"]) DeleteUser(where: _UserWhere!): User @hasScope(scopes: ["User: Delete", "delete:user"]) - MergeUser(where: _UserWhere!, data: _UserData!): User @hasScope(scopes: ["User: Merge", "merge:user"]) + MergeUser(where: _UserKeys!, data: _UserCreate!): User @hasScope(scopes: ["User: Merge", "merge:user"]) } type Query { User: [User] @hasScope(scopes: ["User: Read", "read:user"]) } + input _UserCreate { + idField: ID + name: String + names: [String] + birthday: _Neo4jDateTimeInput + birthdays: [_Neo4jDateTimeInput] + uniqueString: String! + indexedInt: Int + extensionString: String! + } + + input _UserUpdate { + idField: ID + name: String + names: [String] + birthday: _Neo4jDateTimeInput + birthdays: [_Neo4jDateTimeInput] + uniqueString: String + indexedInt: Int + extensionString: String + } + input _UserWhere { AND: [_UserWhere!] OR: [_UserWhere!] @@ -81,15 +103,6 @@ export function cypherTestRunner( uniqueString: String indexedInt: Int } - - input _UserData { - idField: ID - name: String - birthday: _Neo4jDateTimeInput - uniqueString: String - indexedInt: Int - extensionString: String - } `; diff --git a/test/helpers/experimental/testSchema.js b/test/helpers/experimental/testSchema.js index baa44a90..b3f3049d 100644 --- a/test/helpers/experimental/testSchema.js +++ b/test/helpers/experimental/testSchema.js @@ -4,7 +4,9 @@ export const testSchema = ` type User { idField: ID! @id name: String + names: [String] birthday: DateTime + birthdays: [DateTime] uniqueString: String! @unique indexedInt: Int @index liked: [Movie!]! @relation( diff --git a/test/unit/experimental/augmentSchemaTest.test.js b/test/unit/experimental/augmentSchemaTest.test.js index 1bdf89dc..42eeaf5a 100644 --- a/test/unit/experimental/augmentSchemaTest.test.js +++ b/test/unit/experimental/augmentSchemaTest.test.js @@ -115,10 +115,23 @@ test.cb('Test augmented schema', t => { _id: String } - input _UserData { + input _UserCreate { idField: ID name: String + names: [String] birthday: _Neo4jDateTimeInput + birthdays: [_Neo4jDateTimeInput] + uniqueString: String! + indexedInt: Int + extensionString: String! + } + + input _UserUpdate { + idField: ID + name: String + names: [String] + birthday: _Neo4jDateTimeInput + birthdays: [_Neo4jDateTimeInput] uniqueString: String indexedInt: Int extensionString: String @@ -203,6 +216,14 @@ test.cb('Test augmented schema', t => { name_not_starts_with: String name_ends_with: String name_not_ends_with: String + names: [String!] + names_not: [String!] + names_contains: [String!] + names_not_contains: [String!] + names_starts_with: [String!] + names_not_starts_with: [String!] + names_ends_with: [String!] + names_not_ends_with: [String!] birthday: _Neo4jDateTimeInput birthday_not: _Neo4jDateTimeInput birthday_in: [_Neo4jDateTimeInput!] @@ -211,6 +232,12 @@ test.cb('Test augmented schema', t => { birthday_lte: _Neo4jDateTimeInput birthday_gt: _Neo4jDateTimeInput birthday_gte: _Neo4jDateTimeInput + birthdays: [_Neo4jDateTimeInput!] + birthdays_not: [_Neo4jDateTimeInput!] + birthdays_lt: [_Neo4jDateTimeInput!] + birthdays_lte: [_Neo4jDateTimeInput!] + birthdays_gt: [_Neo4jDateTimeInput!] + birthdays_gte: [_Neo4jDateTimeInput!] uniqueString: String uniqueString_not: String uniqueString_in: [String!] @@ -260,7 +287,9 @@ test.cb('Test augmented schema', t => { type User { idField: ID! @id name: String + names: [String] birthday: _Neo4jDateTime + birthdays: [_Neo4jDateTime] uniqueString: String! @unique indexedInt: Int @index liked( @@ -372,7 +401,13 @@ test.cb('Test augmented schema', t => { _id: String } - input _MovieData { + input _MovieCreate { + id: ID + title: String! + genre: MovieGenre + } + + input _MovieUpdate { id: ID title: String genre: MovieGenre @@ -705,7 +740,9 @@ test.cb('Test augmented schema', t => { User( idField: ID name: String + names: [String] birthday: _Neo4jDateTimeInput + birthdays: [_Neo4jDateTimeInput] uniqueString: String indexedInt: Int extensionString: String @@ -818,16 +855,16 @@ test.cb('Test augmented schema', t => { scopes: ["User: Merge", "merge:user", "Movie: Merge", "merge:movie"] ) "[Generated mutation](https://grandstack.io/docs/graphql-schema-generation-augmentation/#create) for [creating](https://neo4j.com/docs/cypher-manual/4.1/clauses/create/#create-nodes) a User node." - CreateUser(data: _UserData!): User + CreateUser(data: _UserCreate!): User @hasScope(scopes: ["User: Create", "create:user"]) "[Generated mutation](https://grandstack.io/docs/graphql-schema-generation-augmentation/#update) for [updating](https://neo4j.com/docs/cypher-manual/4.1/clauses/set/#set-update-a-property) a User node." - UpdateUser(where: _UserWhere!, data: _UserData!): User + UpdateUser(where: _UserWhere!, data: _UserUpdate!): User @hasScope(scopes: ["User: Update", "update:user"]) "[Generated mutation](https://grandstack.io/docs/graphql-schema-generation-augmentation/#delete) for [deleting](https://neo4j.com/docs/cypher-manual/4.1/clauses/delete/#delete-delete-single-node) a User node." DeleteUser(where: _UserWhere!): User @hasScope(scopes: ["User: Delete", "delete:user"]) "[Generated mutation](https://grandstack.io/docs/graphql-schema-generation-augmentation/#merge) for [merging](https://neo4j.com/docs/cypher-manual/4.1/clauses/merge/#query-merge-node-derived) a User node." - MergeUser(where: _UserKeys!, data: _UserData!): User + MergeUser(where: _UserKeys!, data: _UserCreate!): User @hasScope(scopes: ["User: Merge", "merge:user"]) "[Generated mutation](https://grandstack.io/docs/graphql-schema-generation-augmentation/##add--remove-relationship) for [creating](https://neo4j.com/docs/cypher-manual/4.1/clauses/create/#create-relationships) the RATING relationship." AddMovieLikedBy( @@ -921,16 +958,16 @@ test.cb('Test augmented schema', t => { scopes: ["User: Merge", "merge:user", "Movie: Merge", "merge:movie"] ) "[Generated mutation](https://grandstack.io/docs/graphql-schema-generation-augmentation/#create) for [creating](https://neo4j.com/docs/cypher-manual/4.1/clauses/create/#create-nodes) a Movie node." - CreateMovie(data: _MovieData!): Movie + CreateMovie(data: _MovieCreate!): Movie @hasScope(scopes: ["Movie: Create", "create:movie"]) "[Generated mutation](https://grandstack.io/docs/graphql-schema-generation-augmentation/#update) for [updating](https://neo4j.com/docs/cypher-manual/4.1/clauses/set/#set-update-a-property) a Movie node." - UpdateMovie(where: _MovieWhere!, data: _MovieData!): Movie + UpdateMovie(where: _MovieWhere!, data: _MovieUpdate!): Movie @hasScope(scopes: ["Movie: Update", "update:movie"]) "[Generated mutation](https://grandstack.io/docs/graphql-schema-generation-augmentation/#delete) for [deleting](https://neo4j.com/docs/cypher-manual/4.1/clauses/delete/#delete-delete-single-node) a Movie node." DeleteMovie(where: _MovieWhere!): Movie @hasScope(scopes: ["Movie: Delete", "delete:movie"]) "[Generated mutation](https://grandstack.io/docs/graphql-schema-generation-augmentation/#merge) for [merging](https://neo4j.com/docs/cypher-manual/4.1/clauses/merge/#query-merge-node-derived) a Movie node." - MergeMovie(where: _MovieKeys!, data: _MovieData!): Movie + MergeMovie(where: _MovieKeys!, data: _MovieCreate!): Movie @hasScope(scopes: ["Movie: Merge", "merge:movie"]) } diff --git a/test/unit/experimental/cypherTest.test.js b/test/unit/experimental/cypherTest.test.js index 8f22b863..e9bc6a8e 100644 --- a/test/unit/experimental/cypherTest.test.js +++ b/test/unit/experimental/cypherTest.test.js @@ -10,12 +10,16 @@ test('Create node mutation using data input object argument', t => { data: { name: "Michael" indexedInt: 33 + uniqueString: "abc" + extensionString: "xyz" birthday: { year: 1987, month: 9, day: 3, hour: 1 } } ) { idField indexedInt name + uniqueString + extensionString birthday { year month @@ -25,14 +29,16 @@ test('Create node mutation using data input object argument', t => { } `, expectedCypherQuery = ` - CREATE (\`user\`:\`User\` {idField: apoc.create.uuid(),name:$data.name,birthday: datetime($data.birthday),indexedInt:$data.indexedInt}) - RETURN \`user\` { .idField , .indexedInt , .name ,birthday: { year: \`user\`.birthday.year , month: \`user\`.birthday.month , day: \`user\`.birthday.day }} AS \`user\` + CREATE (\`user\`:\`User\` {idField: apoc.create.uuid(),name:$data.name,birthday: datetime($data.birthday),uniqueString:$data.uniqueString,indexedInt:$data.indexedInt,extensionString:$data.extensionString}) + RETURN \`user\` { .idField , .indexedInt , .name , .uniqueString , .extensionString ,birthday: { year: \`user\`.birthday.year , month: \`user\`.birthday.month , day: \`user\`.birthday.day }} AS \`user\` `, expectedParams = { first: -1, offset: 0, data: { name: 'Michael', + uniqueString: 'abc', + extensionString: 'xyz', birthday: { year: { low: 1987, @@ -219,7 +225,7 @@ RETURN \`user\``, ]); }); -test('Merge node mutation using data input object argument', t => { +test('Merge node mutation using data input object argument (creating with .where @id value)', t => { const graphQLQuery = `mutation { MergeUser( where: { @@ -227,9 +233,10 @@ test('Merge node mutation using data input object argument', t => { indexedInt: 33 } data: { - idField: "A" indexedInt: 33 + uniqueString: "abc" name: "Michael" + extensionString: "xyz" birthday: { year: 1987, month: 9, day: 3, hour: 1 } } ) { @@ -245,8 +252,10 @@ test('Merge node mutation using data input object argument', t => { } `, expectedCypherQuery = `MERGE (\`user\`:\`User\`{idField:$where.idField,indexedInt:$where.indexedInt}) -SET \`user\` += {idField:$data.idField,name:$data.name,birthday: datetime($data.birthday),indexedInt:$data.indexedInt} -RETURN \`user\` { .idField , .indexedInt , .name ,birthday: { year: \`user\`.birthday.year , month: \`user\`.birthday.month , day: \`user\`.birthday.day }} AS \`user\``, +ON CREATE + SET \`user\` += {name:$data.name,birthday: datetime($data.birthday),uniqueString:$data.uniqueString,indexedInt:$data.indexedInt,extensionString:$data.extensionString} +ON MATCH + SET \`user\` += {name:$data.name,birthday: datetime($data.birthday),uniqueString:$data.uniqueString,indexedInt:$data.indexedInt,extensionString:$data.extensionString} RETURN \`user\` { .idField , .indexedInt , .name ,birthday: { year: \`user\`.birthday.year , month: \`user\`.birthday.month , day: \`user\`.birthday.day }} AS \`user\``, expectedParams = { where: { idField: 'A', @@ -256,7 +265,6 @@ RETURN \`user\` { .idField , .indexedInt , .name ,birthday: { year: \`user\`.bir } }, data: { - idField: 'A', name: 'Michael', birthday: { year: { @@ -276,10 +284,93 @@ RETURN \`user\` { .idField , .indexedInt , .name ,birthday: { year: \`user\`.bir high: 0 } }, + uniqueString: 'abc', + indexedInt: { + low: 33, + high: 0 + }, + extensionString: 'xyz' + } + }; + + t.plan(4); + return Promise.all([ + cypherTestRunner(t, graphQLQuery, {}, expectedCypherQuery, expectedParams), + augmentedSchemaCypherTestRunner( + t, + graphQLQuery, + {}, + expectedCypherQuery, + expectedParams + ) + ]); +}); + +test('Merge node mutation using data input object argument (creating with .data @id value)', t => { + const graphQLQuery = `mutation { + MergeUser( + where: { + indexedInt: 33 + } + data: { + idField: "A" + indexedInt: 33 + uniqueString: "abc" + name: "Michael" + extensionString: "xyz" + birthday: { year: 1987, month: 9, day: 3, hour: 1 } + } + ) { + idField + indexedInt + name + birthday { + year + month + day + } + } + } + `, + expectedCypherQuery = `MERGE (\`user\`:\`User\`{indexedInt:$where.indexedInt}) +ON CREATE + SET \`user\` += {idField:$data.idField,name:$data.name,birthday: datetime($data.birthday),uniqueString:$data.uniqueString,indexedInt:$data.indexedInt,extensionString:$data.extensionString} +ON MATCH + SET \`user\` += {idField:$data.idField,name:$data.name,birthday: datetime($data.birthday),uniqueString:$data.uniqueString,indexedInt:$data.indexedInt,extensionString:$data.extensionString} RETURN \`user\` { .idField , .indexedInt , .name ,birthday: { year: \`user\`.birthday.year , month: \`user\`.birthday.month , day: \`user\`.birthday.day }} AS \`user\``, + expectedParams = { + where: { indexedInt: { low: 33, high: 0 } + }, + data: { + idField: 'A', + name: 'Michael', + birthday: { + year: { + low: 1987, + high: 0 + }, + month: { + low: 9, + high: 0 + }, + day: { + low: 3, + high: 0 + }, + hour: { + low: 1, + high: 0 + } + }, + uniqueString: 'abc', + indexedInt: { + low: 33, + high: 0 + }, + extensionString: 'xyz' } }; @@ -296,6 +387,109 @@ RETURN \`user\` { .idField , .indexedInt , .name ,birthday: { year: \`user\`.bir ]); }); +test('Merge node mutation using multiple keys, generated @id property, and query variables (generating @id value)', t => { + const graphQLQuery = `mutation MergeUser($where: _UserKeys!, $data: _UserCreate!) { + MergeUser( + where: $where, + data: $data + ) { + idField # ON CREATE: generated using apoc.create.uuid() + indexedInt + name + names + uniqueString + extensionString + birthday { + year + month + day + } + } + } + `, + graphqlParams = { + where: { + // Generated ON CREATE if no value provided for @id key + // "idField": "123", + // @unique key field + uniqueString: 'abc', + // @index key field + indexedInt: 33 + }, + data: { + // keys to set on create or match + uniqueString: 'abc', + indexedInt: 33, + // optional + name: 'Michael', + birthday: { year: 1987, month: 9, day: 3, hour: 1 }, + names: ['A', 'B'], + // required (non-null) + extensionString: 'xyz' + } + }, + expectedCypherQuery = `MERGE (\`user\`:\`User\`{uniqueString:$where.uniqueString,indexedInt:$where.indexedInt}) +ON CREATE + SET \`user\` += {name:$data.name,names:$data.names,birthday: datetime($data.birthday),uniqueString:$data.uniqueString,indexedInt:$data.indexedInt,extensionString:$data.extensionString,idField: apoc.create.uuid()} +ON MATCH + SET \`user\` += {name:$data.name,names:$data.names,birthday: datetime($data.birthday),uniqueString:$data.uniqueString,indexedInt:$data.indexedInt,extensionString:$data.extensionString} RETURN \`user\` { .idField , .indexedInt , .name , .names , .uniqueString , .extensionString ,birthday: { year: \`user\`.birthday.year , month: \`user\`.birthday.month , day: \`user\`.birthday.day }} AS \`user\``, + expectedParams = { + where: { + uniqueString: 'abc', + indexedInt: { + low: 33, + high: 0 + } + }, + data: { + name: 'Michael', + names: ['A', 'B'], + birthday: { + year: { + low: 1987, + high: 0 + }, + month: { + low: 9, + high: 0 + }, + day: { + low: 3, + high: 0 + }, + hour: { + low: 1, + high: 0 + } + }, + uniqueString: 'abc', + indexedInt: { + low: 33, + high: 0 + }, + extensionString: 'xyz' + } + }; + + t.plan(4); + return Promise.all([ + cypherTestRunner( + t, + graphQLQuery, + graphqlParams, + expectedCypherQuery, + expectedParams + ), + augmentedSchemaCypherTestRunner( + t, + graphQLQuery, + graphqlParams, + expectedCypherQuery, + expectedParams + ) + ]); +}); + test('Add relationship mutation using complex node selection arguments', t => { const graphQLQuery = `mutation { AddUserRated(