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

Passing context to @cypher, scalar payload mutations, fixes #200

Merged
merged 16 commits into from
Feb 18, 2019
Merged
9 changes: 4 additions & 5 deletions src/augment.js
Original file line number Diff line number Diff line change
@@ -206,7 +206,6 @@ const augmentResolvers = (augmentedTypeMap, resolvers, config) => {
const possiblyAddOrderingArgument = (args, fieldName) => {
const orderingType = `_${fieldName}Ordering`;
if (args.findIndex(e => e.name.value === orderingType) === -1) {
// TODO refactor
args.push({
kind: 'InputValueDefinition',
name: {
@@ -1370,10 +1369,10 @@ const shouldAugmentRelationField = (config, rootType, fromName, toName) =>
shouldAugmentType(config, rootType, toName);

const fieldIsNotIgnored = (astNode, field, resolvers) => {
return (
!getFieldDirective(field, 'neo4j_ignore') &&
!getCustomFieldResolver(astNode, field, resolvers)
);
return !getFieldDirective(field, 'neo4j_ignore');
// FIXME: issue related to inferences on AST field .resolve
// See: possiblyAddIgnoreDirective
// !getCustomFieldResolver(astNode, field, resolvers)
};

const isNotSystemField = name => {
9 changes: 5 additions & 4 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -5,8 +5,7 @@ import {
extractTypeMapFromTypeDefs,
addDirectiveDeclarations,
printTypeMap,
getQuerySelections,
getMutationSelections
getPayloadSelections
} from './utils';
import {
extractTypeMapFromSchema,
@@ -67,9 +66,10 @@ export function cypherQuery(
) {
const { typeName, variableName } = typeIdentifiers(resolveInfo.returnType);
const schemaType = resolveInfo.schema.getType(typeName);
const selections = getQuerySelections(resolveInfo);
const selections = getPayloadSelections(resolveInfo);
return translateQuery({
resolveInfo,
context,
schemaType,
selections,
variableName,
@@ -89,9 +89,10 @@ export function cypherMutation(
) {
const { typeName, variableName } = typeIdentifiers(resolveInfo.returnType);
const schemaType = resolveInfo.schema.getType(typeName);
const selections = getMutationSelections(resolveInfo);
const selections = getPayloadSelections(resolveInfo);
return translateMutation({
resolveInfo,
context,
schemaType,
selections,
variableName,
27 changes: 21 additions & 6 deletions src/selections.js
Original file line number Diff line number Diff line change
@@ -31,6 +31,7 @@ import {

export function buildCypherSelection({
initial,
cypherParams,
selections,
variableName,
schemaType,
@@ -60,6 +61,7 @@ export function buildCypherSelection({

let tailParams = {
selections: tailSelections,
cypherParams,
variableName,
schemaType,
resolveInfo,
@@ -104,9 +106,12 @@ export function buildCypherSelection({
}

const commaIfTail = tailSelections.length > 0 ? ',' : '';

const isScalarSchemaType = isGraphqlScalarType(schemaType);
const schemaTypeField = !isScalarSchemaType
? schemaType.getFields()[fieldName]
: {};
// Schema meta fields(__schema, __typename, etc)
if (!schemaType.getFields()[fieldName]) {
if (!isScalarSchemaType && !schemaTypeField) {
return recurse({
initial: tailSelections.length
? initial
@@ -115,7 +120,8 @@ export function buildCypherSelection({
});
}

const fieldType = schemaType.getFields()[fieldName].type;
const fieldType =
schemaTypeField && schemaTypeField.type ? schemaTypeField.type : {};
const innerSchemaType = innerType(fieldType); // for target "type" aka label

if (
@@ -166,6 +172,7 @@ export function buildCypherSelection({
return recurse({
initial: `${initial}${fieldName}: apoc.cypher.runFirstColumn("${customCypher}", ${cypherDirectiveArgs(
variableName,
cypherParams,
headSelection,
schemaType,
resolveInfo
@@ -192,7 +199,10 @@ export function buildCypherSelection({
});
}
// We have a graphql object type
const innerSchemaTypeAstNode = typeMap[innerSchemaType].astNode;
const innerSchemaTypeAstNode =
innerSchemaType && typeMap[innerSchemaType]
? typeMap[innerSchemaType].astNode
: {};
const innerSchemaTypeRelation = getRelationTypeDirectiveArgs(
innerSchemaTypeAstNode
);
@@ -213,7 +223,7 @@ export function buildCypherSelection({
const skipLimit = computeSkipLimit(headSelection, resolveInfo.variableValues);

const subSelections = extractSelections(
headSelection.selectionSet.selections,
headSelection.selectionSet ? headSelection.selectionSet.selections : [],
resolveInfo.fragments
);

@@ -223,6 +233,7 @@ export function buildCypherSelection({
variableName: nestedVariable,
schemaType: innerSchemaType,
resolveInfo,
cypherParams,
parentSelectionInfo: {
fieldName,
schemaType,
@@ -235,7 +246,10 @@ export function buildCypherSelection({
});

let selection;
const fieldArgs = schemaType.getFields()[fieldName].args.map(e => e.astNode);
const fieldArgs =
!isScalarSchemaType && schemaTypeField && schemaTypeField.args
? schemaTypeField.args.map(e => e.astNode)
: [];
const temporalArgs = getTemporalArguments(fieldArgs);
const queryParams = paramsToString(
innerFilterParams(filterParams, temporalArgs)
@@ -259,6 +273,7 @@ export function buildCypherSelection({
selection = recurse(
customCypherField({
...fieldInfo,
cypherParams,
schemaType,
schemaTypeRelation,
customCypher,
64 changes: 57 additions & 7 deletions src/translate.js
Original file line number Diff line number Diff line change
@@ -30,14 +30,16 @@ import {
splitSelectionParameters,
getTemporalArguments,
temporalPredicateClauses,
isTemporalType
isTemporalType,
isGraphqlScalarType
} from './utils';
import { getNamedType } from 'graphql';
import { buildCypherSelection } from './selections';
import _ from 'lodash';

export const customCypherField = ({
customCypher,
cypherParams,
schemaTypeRelation,
initial,
fieldName,
@@ -62,6 +64,7 @@ export const customCypherField = ({
fieldIsList ? '' : 'head('
}[ ${nestedVariable} IN apoc.cypher.runFirstColumn("${customCypher}", ${cypherDirectiveArgs(
variableName,
cypherParams,
headSelection,
schemaType,
resolveInfo
@@ -368,7 +371,7 @@ export const temporalField = ({
// containing this temporal field was a node
let variableName = parentVariableName;
let fieldIsArray = isArrayType(parentFieldType);
if (!isNodeType(parentSchemaType.astNode)) {
if (parentSchemaType && !isNodeType(parentSchemaType.astNode)) {
// initial assumption wrong, build appropriate relationship variable
if (
isRootSelection({
@@ -474,6 +477,7 @@ export const temporalType = ({
// Query API root operation branch
export const translateQuery = ({
resolveInfo,
context,
selections,
variableName,
typeName,
@@ -493,13 +497,15 @@ export const translateQuery = ({
const queryArgs = getQueryArguments(resolveInfo);
const temporalArgs = getTemporalArguments(queryArgs);
const queryTypeCypherDirective = getQueryCypherDirective(resolveInfo);
const cypherParams = getCypherParams(context);
const queryParams = paramsToString(
innerFilterParams(
filterParams,
temporalArgs,
null,
queryTypeCypherDirective ? true : false
)
),
cypherParams
);
const safeVariableName = safeVar(variableName);
const temporalClauses = temporalPredicateClauses(
@@ -509,9 +515,11 @@ export const translateQuery = ({
);
const outerSkipLimit = getOuterSkipLimit(first);
const orderByValue = computeOrderBy(resolveInfo, selections);

if (queryTypeCypherDirective) {
return customQuery({
resolveInfo,
cypherParams,
schemaType,
argString: queryParams,
selections,
@@ -525,6 +533,7 @@ export const translateQuery = ({
} else {
return nodeQuery({
resolveInfo,
cypherParams,
schemaType,
argString: queryParams,
selections,
@@ -542,9 +551,19 @@ export const translateQuery = ({
}
};

const getCypherParams = context => {
return context &&
context.cypherParams &&
context.cypherParams instanceof Object &&
Object.keys(context.cypherParams).length > 0
? context.cypherParams
: undefined;
};

// Custom read operation
const customQuery = ({
resolveInfo,
cypherParams,
schemaType,
argString,
selections,
@@ -558,27 +577,40 @@ const customQuery = ({
const safeVariableName = safeVar(variableName);
const [subQuery, subParams] = buildCypherSelection({
initial: '',
cypherParams,
selections,
variableName,
schemaType,
resolveInfo,
paramIndex: 1
});
const params = { ...nonNullParams, ...subParams };
if (cypherParams) {
params['cypherParams'] = cypherParams;
}
// QueryType with a @cypher directive
const cypherQueryArg = queryTypeCypherDirective.arguments.find(x => {
return x.name.value === 'statement';
});
const isScalarType = isGraphqlScalarType(schemaType);
const temporalType = isTemporalType(schemaType.name);
const query = `WITH apoc.cypher.runFirstColumn("${
cypherQueryArg.value.value
}", ${argString || 'null'}, True) AS x UNWIND x AS ${safeVariableName}
RETURN ${safeVariableName} {${subQuery}} AS ${safeVariableName}${orderByValue} ${outerSkipLimit}`;
}", ${argString ||
'null'}, True) AS x UNWIND x AS ${safeVariableName} RETURN ${safeVariableName} ${
// Don't add subQuery for scalar type payloads
// FIXME: fix subselection translation for temporal type payload
!temporalType && !isScalarType
? `{${subQuery}} AS ${safeVariableName}${orderByValue}`
: ''
} ${outerSkipLimit}`;
return [query, params];
};

// Generated API
const nodeQuery = ({
resolveInfo,
cypherParams,
schemaType,
selections,
variableName,
@@ -596,13 +628,17 @@ const nodeQuery = ({
const safeLabelName = safeLabel(typeName);
const [subQuery, subParams] = buildCypherSelection({
initial: '',
cypherParams,
selections,
variableName,
schemaType,
resolveInfo,
paramIndex: 1
});
const params = { ...nonNullParams, ...subParams };
if (cypherParams) {
params['cypherParams'] = cypherParams;
}
const arrayParams = _.pickBy(filterParams, Array.isArray);
const args = innerFilterParams(filterParams, temporalArgs);

@@ -642,6 +678,7 @@ const nodeQuery = ({
// Mutation API root operation branch
export const translateMutation = ({
resolveInfo,
context,
schemaType,
selections,
variableName,
@@ -669,6 +706,7 @@ export const translateMutation = ({
if (mutationTypeCypherDirective) {
return customMutation({
...mutationInfo,
context,
mutationTypeCypherDirective,
variableName,
orderByValue,
@@ -712,6 +750,7 @@ export const translateMutation = ({
// Custom write operation
const customMutation = ({
params,
context,
mutationTypeCypherDirective,
selections,
variableName,
@@ -720,6 +759,7 @@ const customMutation = ({
orderByValue,
outerSkipLimit
}) => {
const cypherParams = getCypherParams(context);
const safeVariableName = safeVar(variableName);
// FIXME: support IN for multiple values -> WHERE
const argString = paramsToString(
@@ -728,7 +768,8 @@ const customMutation = ({
null,
null,
true
)
),
cypherParams
);
const cypherQueryArg = mutationTypeCypherDirective.arguments.find(x => {
return x.name.value === 'statement';
@@ -741,12 +782,21 @@ const customMutation = ({
resolveInfo,
paramIndex: 1
});
const isScalarType = isGraphqlScalarType(schemaType);
const temporalType = isTemporalType(schemaType.name);
params = { ...params, ...subParams };
if (cypherParams) {
params['cypherParams'] = cypherParams;
}
const query = `CALL apoc.cypher.doIt("${
cypherQueryArg.value.value
}", ${argString}) YIELD value
WITH apoc.map.values(value, [keys(value)[0]])[0] AS ${safeVariableName}
RETURN ${safeVariableName} {${subQuery}} AS ${safeVariableName}${orderByValue} ${outerSkipLimit}`;
RETURN ${safeVariableName} ${
!temporalType && !isScalarType
? `{${subQuery}} AS ${safeVariableName}${orderByValue} ${outerSkipLimit}`
: ''
}`;
return [query, params];
};

Loading