1

In the following cypher query, whenever the $dlt parameter is false, the query never continues beyond the DETACH DELETE statement:

MATCH (person:Person {id: $id})
SET person.matched = (CASE person.secret WHEN $secret THEN 1 ELSE 0 END)
WITH person WHERE person.matched = 1 AND $dlt = true
MATCH (person)<-[:UsedBy]-(dev:Device)
DETACH DELETE dev
WITH person WHERE person.matched = 1
MERGE (person)<-[r:UsedBy {assignedDate: dateTime()}]-(device:Device {id: 'efgh', firebaseToken: 'jjjj8888'})
WITH person, person.matched as matched
REMOVE person.matched
RETURN
CASE matched
WHEN 0 
THEN null
ELSE person END AS Person

The idea is that all Device nodes (and connecting edges) needs to be removed only when the $dlt is true. However, regardless of $dlt (and this is what is not happening) - the subsequent parts must continue (adding a new Device node with a connecting edge, deleting the temporary matched property from person and returning based on matched value).

Btw, I'm running this query from a Jupyter Notebook against an AWS Neptune DB, with the %%oc magic command on top. As this is just for testing, I am not really using parameters (e.g. $dlt) in the Jupyter Notebook, but rather hard-coding some values.

What am I missing?

Mor Sagmon
  • 905
  • 1
  • 16
  • 35
  • Are you using the `%%oc` magic to submit the query or some other way? If using `%%oc` then parameters need to be substituted using the `${x}` syntax. If you are passing the parameters in and submitting the query some other way, can you please update the question to show that part as well? Thanks. – Kelvin Lawrence Mar 01 '23 at 14:41
  • Thanks @Kelvin. Updated the question. Queries are running great until I have some conditional flow issues (as explained in the question above) – Mor Sagmon Mar 01 '23 at 14:55
  • If `$dlt` is `false` the query is going to end, as nothing will flow out from the `WITH` clause as `false` is never equal to `true`. Perhaps try re-ordering the query to do the mandatory parts first and then do any optional parts. – Kelvin Lawrence Mar 01 '23 at 16:38
  • I see. Hmmm. I am facing flow issues in many queries. What's the proper way to implement conditional blocks, such as If X then do y but if a then do b, and always flow on to the last RETURN statement? – Mor Sagmon Mar 01 '23 at 17:17
  • Hopefully the suggestions in the two answers below will give you some template queries to build from. – Kelvin Lawrence Mar 02 '23 at 00:05

2 Answers2

1

[UPDATED]

openCypher/Neptune answer

Since openCypher is a limited version of Cypher, the simplest thing would be to split this particular query in two: do the optional deletion in one query and then the optional merge in another. If the 2 queries should be done together atomically, you can use a Mutation Bolt transaction query, and that transaction code can return the appropriate value after it performs both individual queries.

Or you can explore clever ways to use OPTIONAL MATCH, FOREACH, and so forth. This question and its answer may be instructive.

Cypher answer

You can use CALL subqueries to do conditional processing that does not abort the rest of the query.

For example, the following version of your query might work for you. It uses "unit subqueries". A unit subquery does not return anything and does not affect the rows being processed by the enclosing query.

Note that I also simplified your query by not bothering to set and then remove the temporary matched property on each Person, which is wasteful of time and resources. Also, there was no reason to treat booleans as integers.

MATCH (person:Person {id: $id})
WITH person, (person.secret = $secret) AS matched
CALL {
  WITH person, matched
  WITH person, matched
  WHERE matched AND $dlt
  MATCH (person)<-[:UsedBy]-(dev:Device)
  DETACH DELETE dev
}
CALL {
  WITH person, matched
  WITH person, matched
  WHERE matched
  MERGE (person)<-[r:UsedBy {assignedDate: dateTime()}]-(device:Device {id: 'efgh', firebaseToken: 'jjjj8888'})
}
RETURN (CASE WHEN matched THEN null ELSE person END) AS Person

If/Else Processing

As a bonus, the following is an simple example of how to use post-union processing to do "if/else" processing that does not abort the rest of the enclosing query. In this example, the subquery is not a "unit subquery" and returns a type variable that is visible to the enclosing query.

UNWIND [1,2,3,4,5] AS x
CALL {
    WITH x
    WITH x
    WHERE x % 2 = 0
    RETURN 'even' AS type
  UNION
    WITH x
    WITH x
    WHERE x % 2 <> 0
    RETURN 'odd' AS type
}
WITH x, type, x^2 AS squared //arbitrary post-subquery processing
RETURN x, type, squared
ORDER BY x
cybersam
  • 63,203
  • 6
  • 53
  • 76
  • Amazon Neptune does not currently support CALL subqueries (which are not part of the openCypher spec AFAIK) which is why I was trying to stay within those boundaries in the discussions above. – Kelvin Lawrence Mar 01 '23 at 20:29
  • Ah. I would suggest that you use "opencypher" instead of "Cypher" in your question to make it super clear. But at least you can improve your query a bit. In your use case there is no need to set and remove temporary properties, and booleans should just be used as-is. These suggestions will not solve your main problem, though. Unfortunately, `opencyper` is not very powerful at this point, and will probably always lag behind Cypher. – cybersam Mar 01 '23 at 20:52
  • 1
    See my updated answer for a suggested solution to this use case. – cybersam Mar 01 '23 at 21:32
  • Many thanks @cybersam. btw. ForEach is also not supported by openquery, and I don't want to use bolt just for this issue. Yes, atomicity is important. I just duplicated and have two versions of the query, conditionally calling one or the other from the app. – Mor Sagmon Mar 06 '23 at 07:44
0

Based on the discussions and some thinking about how OPTIONAL MATCH can help along with some query re-ordering, here is a version that should work on Neptune. In this case, if there is an OPTIONAL MATCH result and $dlt is TRUE, the dev will get deleted. If there is no match (including if $dlt is FALSE) then dev should be null and therefore nothing will get deleted. The rest of the query after the DELETE line should continue to the end either way.

MATCH (person:Person {id: $id})
SET person.matched = (CASE person.secret WHEN $secret THEN 1 ELSE 0 END)
WITH person WHERE person.matched = 1
OPTIONAL MATCH (person)<-[:UsedBy]-(dev:Device)
WHERE $dlt = true
DETACH DELETE dev
WITH person WHERE person.matched = 1
MERGE (person)<-[r:UsedBy {assignedDate: dateTime()}]-(device:Device {id: 'efgh', firebaseToken: 'jjjj8888'})
WITH person, person.matched as matched
REMOVE person.matched
RETURN
CASE matched
WHEN 0 
THEN null
ELSE person END AS Person
Kelvin Lawrence
  • 14,674
  • 2
  • 16
  • 38
  • Thank you @kelvin. I dont see how this will work, as the optional query may return a valid path, and the following WHERE may be false and break the flow beyond that point. Anyway, I ended up duplicating the query and conditionally call one or the other from the app. – Mor Sagmon Mar 06 '23 at 07:46