Open alexluna7 opened 4 years ago
Hi @alexluna7 can you please share the relevant bits of your GraphQL type defintions, including the @cypher
schema directive and the GraphQL query that produces the error so we can try to replicate the problem>
@johnymontana - I've also ran in this bug using the following GraphQL typedefs:
interface Entity {
name: String!
}
type Place implements Entity {
name: String!
visitors: [Visited]
}
type Person implements Entity {
name: String!
visited: [Visited]
}
type Visited @relation(name: "VISITED") {
from: Person
to: Place
# List of sentence IDs which include this action
sentences: [String]
}
type Sentence {
id: String!
relations: [EntEntRel] @cypher(statement:"""
MATCH (source:Entity)-[r]->(target:Entity)
WHERE size([x IN r.sentences WHERE x = this.id ]) > 0
RETURN r {
.*, type: type(r),
source: (source { .*, FRAGMENT_TYPE: head([ x IN labels(source) WHERE x <> 'Entity' ]) } ),
target: (target { .*, FRAGMENT_TYPE: head([ x IN labels(target) WHERE x <> 'Entity' ]) } )
}
""")
}
# Fake type artificially constructed by cypher query
type EntEntRel {
type: String!
source: Entity
target: Entity
sentence: [String]
}
The following GraphQL query produces the error:
query {
Sentence {
id
relations {
type
source { name }
target { name }
}
}
}
Using DEBUG=neo4j-graphql-js
shows that the generated Cypher query is:
MATCH (`sentence`:`Sentence`) RETURN `sentence` { .id ,relations: [ sentence_relations IN apoc.cypher.runFirstColumn("MATCH (source:Entity)-[r]->(target:Entity)
WHERE size([x IN r.sentences WHERE x = this.id ]) > 0
RETURN r {
.*, type: type(r),
source: (source { .*, FRAGMENT_TYPE: head([ x IN labels(source) WHERE x <> 'Entity' ]) } ),
target: (target { .*, FRAGMENT_TYPE: head([ x IN labels(target) WHERE x <> 'Entity' ]) } )
}", {this: sentence}, true) | sentence_relations { undefined }] } AS `sentence`
I've managed to track down the cause of this bug to the variable subQuery
being undefined on this line: https://github.com/neo4j-graphql/neo4j-graphql-js/blob/master/src/translate.js#L1363
Updating the line from:
mapProjection = `${safeVariableName} {${subQuery}}`;
to
mapProjection = `${safeVariableName} {${subQuery || ' .* '}}`;
causes the graphql query to work as expected - but I don't know if this an acceptable fix (I don't have context on how this repo works to know why subQuery
is undefined here
Hi @alexluna7 can you please share the relevant bits of your GraphQL type defintions, including the
@cypher
schema directive and the GraphQL query that produces the error so we can try to replicate the problem>
@johnymontana You will find below the requested details:
The following mutations correspond to the node console output in my first post, the 1 is SetupLanguage and the 2 is SetupCountry.
extend type Mutation { SetupLanguage(input: [LanguageInput!]!): [LanguageResponse!]! @cypher( statement: """ WITH $input as input FOREACH (language IN input | MERGE (l:Language{code:language.code}) FOREACH (name IN language.name | MERGE (nl:Language{code:name.language}) MERGE (l)-[:NAME_TRANSLATION]->(t:Translation)-[:IN_LANGUAGE]->(nl) ON CREATE SET t.id=apoc.create.uuid() SET t.text=name.text ) FOREACH (root IN language.root | MERGE (r:Language{code:root.code}) MERGE (l)-[:ROOT_LANGUAGE]->(r) ) ) WITH input UNWIND input AS language MATCH (l:Language{code:language.code})-[:NAME_TRANSLATION]->(t:Translation)-[:IN_LANGUAGE]->(nl:Language) OPTIONAL MATCH (l)-[:ROOT_LANGUAGE]->(r:Language) RETURN l { .code, name: t { .id, language: nl.code, .text }, root: r.code } AS languageResponse """ ) SetupCountry(input: [CountryInput!]!): [CountryResponse!]! @cypher( statement: """ WITH $input as input MERGE (r:Root) ON CREATE SET r.id=apoc.create.uuid() FOREACH (country IN input | MERGE (c:Country{code:country.code}) FOREACH (name IN country.name | MERGE (l:Language{code:name.language}) MERGE (c)-[:NAME_TRANSLATION]->(t:Translation)-[:IN_LANGUAGE]->(l) ON CREATE SET t.id=apoc.create.uuid() SET t.text=name.text ) FOREACH (nid IN country.nid | MERGE (c)-[:HAS_NID{code:nid.code}]->(comp:Component)-[:CURRENT]->(ver:Version:Unique)-[:VERSION_OF]->(comp) ON CREATE SET comp.id=apoc.create.uuid(), ver.id=apoc.create.uuid() SET comp.alias=nid.code MERGE (comp)<-[:IS_AUTHOR]-(r) FOREACH (nname IN nid.name | MERGE (nl:Language{code:nname.language}) MERGE (comp)-[:NAME_TRANSLATION]->(nt:Translation)-[:IN_LANGUAGE]->(nl) ON CREATE SET nt.id=apoc.create.uuid() SET nt.text=nname.text ) ) FOREACH (official IN country.official | MERGE (ol:Language{code:official.code}) MERGE (c)-[:OFFICIAL_LANGUAGE]->(ol) ) FOREACH (supported IN country.supported | MERGE (sl:Language{code:supported.code}) MERGE (c)-[:SUPPORTED_LANGUAGE]->(sl) ) ) WITH input UNWIND input AS country MATCH (c:Country{code:country.code}) RETURN c { .code, name: [(c)-[:NAME_TRANSLATION]->(t:Translation)-[:IN_LANGUAGE]->(l:Language) | t { .id, language: l.code, .text }], nid: [(c)-[rel:HAS_NID]->(comp:Component)-[:CURRENT]->(ver:Version:Unique) | rel { .code, name: [(comp)-[:NAME_TRANSLATION]->(t:Translation)-[:IN_LANGUAGE]->(l:Language) | t { .id, language: l.code, .text }], component: comp { .id, .alias, type: 'Unique', current: ver.id, author: [(comp)<-[:IS_AUTHOR]-(a) | a.id ]} }], official: [(c)-[OFFICIAL_LANGUAGE]->(l:Language)-[:NAME_TRANSLATION]->(t:Translation)-[:IN_LANGUAGE]->(nl:Language) | l { .code, name: t { .id, language: nl.code, .text }, root: r.code }], supported: [(c)-[SUPPORTED_LANGUAGE]->(l:Language)-[:NAME_TRANSLATION]->(t:Translation)-[:IN_LANGUAGE]->(nl:Language) | l { .code, name: t { .id, language: nl.code, .text }, root: r.code }] } AS countryResponse """ ) }
And these are all the type definitions related to these mutations:
type Translation { id: ID! language: Language! @relation(name: "IN_LANGUAGE", direction: "OUT") text: String! }
input TranslationInput { language: String! text: String! }
type TranslationResponse { id: ID! language: String! text: String! }
type Language { code: String! name: [Translation!]! @relation(name: "NAME_TRANSLATION", direction: "OUT") root: [Language!] @relation(name: "ROOT_LANGUAGE", direction: "OUT") official: [Country!] @relation(name: "OFFICIAL_LANGUAGE", direction: "IN") supported: [Country!] @relation(name: "SUPPORTED_LANGUAGE", direction: "IN") translations: [Translation!] @relation(name: "IN_LANGUAGE", direction: "IN") }
input LanguageInput { code: String! name: [TranslationInput!]! root: LanguageInput }
type LanguageResponse { code: String! name: [TranslationResponse!]! root: String }
input LanguageNameTranslationInput { code: String! language: String! name: String! }
type Country { code: String! name: [Translation!]! @relation(name: "NAME_TRANSLATION", direction: "OUT") nid: [Component!]! @relation(name: "HAS_NID", direction: "OUT") official: [Language!]! @relation(name: "OFFICIAL_LANGUAGE", direction: "OUT") supported: [Language!] @relation(name: "SUPPORTED_LANGUAGE", direction: "OUT") }
type HasNid @relation(name: "HAS_NID") { code: String! }
input NidInput { code: String! name: [TranslationInput!]! }
type NidResponse { code: String! name: [TranslationResponse!]! component: ComponentResponse! }
input CountryInput { code: String! name: [TranslationInput!]! nid: [NidInput!]! official: [LanguageInput!]! supported: [LanguageInput!] }
type CountryResponse { code: String! name: [TranslationResponse]! nid: [NidResponse!]! official: [LanguageResponse!]! supported: [LanguageResponse!] }
Let me know if you need anything else.
I've ran into this issue again, with a different minimal example, which you can find here: https://community.neo4j.com/t/reusing-custom-cypher-logic-between-all-instances-of-an-interface/23613/3?u=jnterry
Hi @johnymontana, were you able to reproduce the undefined issue or do you need additional data?
As I'm hitting this issue multiple times while working in my project, I've tried to create a very simple scenario to reproduce it. However, I've ended up hitting a different error, which I will reproduce below. I believe that both issues may, somewhat, be connected. It seems to me that neo4j-graphql-js, in some scenarios, doesn't deal well when a @cypher query/mutation has a field that returns an object (therefore a subquery).
As an additional information, I've tried @jnterry code fix suggestion above, but making the change in the dist code installed by npm (version 2.16.0), and it works in the case where I was getting the undefined error.
Please let me know if I am doing something wrong or what I can do to help you go through this issue.
GraphQL schema:
The nidData and nidConfigData fields are there only to help exploring the issue, my use case is more complex than that.
` type UTCountry { code: String! @id name: String! nationalID: UTNid! @relation(name: "UT_HAS_NID", direction: "OUT") nidData: UTNid! @cypher( statement: """ MATCH (this)-[:UT_HAS_NID]->(n:UTNid)-[:UT_NID_CONFIG]->(c:UTConfig) RETURN n { .code, .name, config: c} AS UTNid """ ) nidConfigData: UTConfig @cypher( statement: """ MATCH (this)-[:UT_HAS_NID]->(n:UTNid)-[:UT_NID_CONFIG]->(c:UTConfig) RETURN c AS UTConfig """ ) }
type UTNid { code: String! @id name: String! config: UTConfig @relation(name: "UT_NID_CONFIG", direction: "OUT") }
type UTConfig { id: ID! @id required: Boolean! min: Int max: Int length: Int regexp: String } `
First playground query with result:
` { UTCountry { nidData { code name config { id required min max length regexp } } } }
{
"errors": [
{
"message": "Expected to find a node at 'uTCountry_nidData' but found Map{name -> String(\"CPF\"), code -> String(\"BR_cpf\"), config -> (18)} instead",
"locations": [
{
"line": 2,
"column": 3
}
],
"path": [
"UTCountry"
],
"extensions": {
"code": "INTERNAL_SERVER_ERROR",
"exception": {
"code": "Neo.ClientError.Statement.TypeError",
"name": "Neo4jError",
"stacktrace": [
"Neo4jError: Expected to find a node at 'uTCountry_nidData' but found Map{name -> String(\"CPF\"), code -> String(\"BR_cpf\"), config -> (18)} instead",
": ",
" at captureStacktrace (/home/app/node_modules/neo4j-driver/lib/result.js:263:15)",
" at new Result (/home/app/node_modules/neo4j-driver/lib/result.js:68:19)",
" at newCompletedResult (/home/app/node_modules/neo4j-driver/lib/transaction.js:449:10)",
" at Object.run (/home/app/node_modules/neo4j-driver/lib/transaction.js:287:14)",
" at Transaction.run (/home/app/node_modules/neo4j-driver/lib/transaction.js:123:32)",
" at /home/app/node_modules/neo4j-graphql-js/dist/index.js:192:25",
" at TransactionExecutor._safeExecuteTransactionWork (/home/app/node_modules/neo4j-driver/lib/internal/transaction-executor.js:134:22)",
" at TransactionExecutor._executeTransactionInsidePromise (/home/app/node_modules/neo4j-driver/lib/internal/transaction-executor.js:122:32)",
" at /home/app/node_modules/neo4j-driver/lib/internal/transaction-executor.js:61:15",
" at new Promise (
The first query cypher code from neo4j-graphql-js as outputted in node's console:
neo4j-graphql-js MATCH (
uTCountry:
UTCountry) RETURN
uTCountry{nidData: head([ uTCountry_nidData IN apoc.cypher.runFirstColumn("MATCH (this)-[:UT_HAS_NID]->(n:UTNid)-[:UT_NID_CONFIG]->(c:UTConfig) RETURN n { .code, .name, config: c} AS UTNid", {this: uTCountry}, true) | uTCountry_nidData { .code , .name ,config: head([(
uTCountry_nidData)-[:
UT_NID_CONFIG]->(
uTCountry_nidData_config:
UTConfig) |
uTCountry_nidData_config{ .id , .required , .min , .max , .length , .regexp }]) }]) } AS
uTCountry+0ms neo4j-graphql-js { "offset": 0, "first": -1 } +7ms
The first query result when run from cypher-shell:
` neo4j@neo4j> MATCH (this:UTCountry{code:$country}) MATCH (this)-[:UT_HAS_NID]->(n:UTNid)-[:UT_NID_CONFIG]->(c:UTConfig) RETURN n { .code, .name, config: c} AS UTNid ; +------------------------------------------------------------------------------------------------------------------------------------------------------+ | UTNid | +------------------------------------------------------------------------------------------------------------------------------------------------------+ | {name: "CPF", code: "BR_cpf", config: (:UTConfig {length: 11.0, regexp: "([0-9]{11})", id: "59059aec-9936-4dfc-be51-0f2e154aede2", required: TRUE})} | +------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row available after 44 ms, consumed after another 1 ms `
Second playground query with result:
` { UTCountry { nidConfigData { id required min max length regexp } } }
{ "data": { "UTCountry": [ { "nidConfigData": { "id": "59059aec-9936-4dfc-be51-0f2e154aede2", "required": true, "min": null, "max": null, "length": 11, "regexp": "([0-9]{11})" } } ] } } `
The second query cypher code from neo4j-graphql-js as outputted in node's console:
neo4j-graphql-js MATCH (
uTCountry:
UTCountry) RETURN
uTCountry{nidConfigData: head([ uTCountry_nidConfigData IN apoc.cypher.runFirstColumn("MATCH (this)-[:UT_HAS_NID]->(n:UTNid)-[:UT_NID_CONFIG]->(c:UTConfig) RETURN c AS UTConfig", {this: uTCountry}, true) | uTCountry_nidConfigData { .id , .required , .min , .max , .length , .regexp }]) } AS
uTCountry+1h neo4j-graphql-js { "offset": 0, "first": -1 } +1ms
The second query result when run from cypher-shell:
` neo4j@neo4j> MATCH (this:UTCountry{code:$country}) MATCH (this)-[:UT_HAS_NID]->(n:UTNid)-[:UT_NID_CONFIG]->(c:UTConfig) RETURN c AS UTConfig ; +---------------------------------------------------------------------------------------------------------------+ | UTConfig | +---------------------------------------------------------------------------------------------------------------+ | (:UTConfig {length: 11.0, regexp: "([0-9]{11})", id: "59059aec-9936-4dfc-be51-0f2e154aede2", required: TRUE}) | +---------------------------------------------------------------------------------------------------------------+
1 row available after 88 ms, consumed after another 1 ms `
The following issue may be connected as well:
https://community.neo4j.com/t/nested-query-is-undefined/16507
Anything new about this ?
Hitting this error on the following toy example that attempts to return node-links style data:
CREATE (a:Node {id: "Alice"}), (b:Node {id: "Bob"}), (c:Node {id: "Carol"}), (a)-[:KNOWS]->(b), (b)-[:KNOWS]->(c);
type NodeObject {
id: ID!
}
type LinkObject {
source: ID!
target: ID!
}
type DaveGraph {
nodes: [NodeObject]
links: [LinkObject]
}
type Query {
daveGraph: DaveGraph @cypher(
statement: """
MATCH (n), (a)-[r]->(b)
WITH COLLECT(DISTINCT n) AS nodes, COLLECT(DISTINCT {source: a.id, target: b.id}) AS links
RETURN {
nodes: nodes,
links: links
}
"""
)
}
Example query:
{
daveGraph {
nodes {
id
}
links {
source
target
}
}
}
Here's the error:
{
"errors": [
{
"message": "Variable `undefined` not defined (line 6, column 92 (offset: 283))\n\"}\", {offset:$offset, first:$first}, True) AS x UNWIND x AS `daveGraph` RETURN `daveGraph` {undefined} AS `daveGraph`\"\n ^",
"locations": [
{
"line": 2,
"column": 3
}
],
"path": [
"daveGraph"
],
"extensions": {
"code": "INTERNAL_SERVER_ERROR",
"exception": {
"code": "Neo.ClientError.Statement.SyntaxError",
"name": "Neo4jError",
"stacktrace": [
"Neo4jError: Variable `undefined` not defined (line 6, column 92 (offset: 283))",
"\"}\", {offset:$offset, first:$first}, True) AS x UNWIND x AS `daveGraph` RETURN `daveGraph` {undefined} AS `daveGraph`\"",
" ^",
"",
" at captureStacktrace (/home/amoe/dev/lysander-graphql-server/node_modules/neo4j-driver/lib/result.js:275:15)",
" at new Result (/home/amoe/dev/lysander-graphql-server/node_modules/neo4j-driver/lib/result.js:66:19)",
" at newCompletedResult (/home/amoe/dev/lysander-graphql-server/node_modules/neo4j-driver/lib/transaction.js:446:10)",
" at Object.run (/home/amoe/dev/lysander-graphql-server/node_modules/neo4j-driver/lib/transaction.js:285:14)",
" at Transaction.run (/home/amoe/dev/lysander-graphql-server/node_modules/neo4j-driver/lib/transaction.js:121:32)",
" at /home/amoe/dev/lysander-graphql-server/node_modules/neo4j-graphql-js/dist/index.js:190:25",
" at TransactionExecutor._safeExecuteTransactionWork (/home/amoe/dev/lysander-graphql-server/node_modules/neo4j-driver/lib/internal/transaction-executor.js:132:22)",
" at TransactionExecutor._executeTransactionInsidePromise (/home/amoe/dev/lysander-graphql-server/node_modules/neo4j-driver/lib/internal/transaction-executor.js:120:32)",
" at /home/amoe/dev/lysander-graphql-server/node_modules/neo4j-driver/lib/internal/transaction-executor.js:59:15",
" at new Promise (<anonymous>)"
]
}
}
}
],
"data": {
"daveGraph": null
}
}
I have two mutations in GraphQL using @cypher custom code that are enclosed by additional code cypher code from neo4-graphql-js that is adding an {undefined} map to the final RETURN clause leading to an error.
When I run the same code (my part) directly in cypher-shell it runs smoothly.
Below is the output of node's console for both mutations:
1. neo4j-graphql-js CALL apoc.cypher.doIt("WITH $input as input FOREACH (language IN input | MERGE (l:Language{code:language.code}) FOREACH (name IN language.name | MERGE (nl:Language{code:name.language}) MERGE (l)-[:NAME_TRANSLATION]->(t:Translation)-[:IN_LANGUAGE]->(nl) ON CREATE SET t.id=apoc.create.uuid() SET t.text=name.text ) FOREACH (root IN language.root | MERGE (r:Language{code:root.code}) MERGE (l)-[:ROOT_LANGUAGE]->(r) ) ) WITH input UNWIND input AS language MATCH (l:Language{code:language.code})-[:NAME_TRANSLATION]->(t:Translation)-[:IN_LANGUAGE]->(nl:Language) OPTIONAL MATCH (l)-[:ROOT_LANGUAGE]->(r:Language) RETURN l", {input:$input, first:$first, offset:$offset}) YIELD value WITH apoc.map.values(value, [keys(value)[0]])[0] AS
languagePayload
RETURNlanguagePayload
{undefined} ASlanguagePayload
+0ms2. neo4j-graphql-js CALL apoc.cypher.doIt("WITH $input as input MERGE (r:Root) ON CREATE SET r.id=apoc.create.uuid() FOREACH (country IN input | MERGE (c:Country{code:country.code}) FOREACH (name IN country.name | MERGE (l:Language{code:name.language}) MERGE (c)-[:NAME_TRANSLATION]->(t:Translation)-[:IN_LANGUAGE]->(l) ON CREATE SET t.id=apoc.create.uuid() SET t.text=name.text ) FOREACH (nid IN country.nid | MERGE (c)-[:HAS_NID{code:nid.code}]->(comp:Component)-[:CURRENT]->(ver:Version:Unique)-[:VERSION_OF]->(comp) ON CREATE SET comp.id=apoc.create.uuid(), ver.id=apoc.create.uuid() MERGE (comp)<-[:IS_AUTHOR]-(r) FOREACH (nname IN nid.name | MERGE (nl:Language{code:nname.language}) MERGE (comp)-[:NAME_TRANSLATION]->(nt:Translation)-[:IN_LANGUAGE]->(nl) ON CREATE SET nt.id=apoc.create.uuid() SET nt.text=nname.text ) ) FOREACH (official IN country.official | MERGE (ol:Language{code:official.code}) MERGE (c)-[:OFFICIAL_LANGUAGE]->(ol) ) FOREACH (supported IN country.supported | MERGE (sl:Language{code:supported.code}) MERGE (c)-[:SUPPORTED_LANGUAGE]->(sl) ) ) WITH input UNWIND input AS country MATCH (c:Country{code:country.code}) RETURN c", {input:$input, first:$first, offset:$offset}) YIELD value WITH apoc.map.values(value, [keys(value)[0]])[0] AS
countryPayload
RETURNcountryPayload
{undefined} AScountryPayload
+159msIs this a bug?