Base data creation:
CREATE
(l:List {id: 42, name: "Stuff"}),
(i1:Item {id: "a", description: "Fry"}),
(i2:Item {id: "b", description: "Bender"}),
(i3:Item {id: "c", description: "Leela"}),
(i1)-[:IN_LIST {position: 0}]->(l),
(i2)-[:IN_LIST {position: 1}]->(l),
(i3)-[:IN_LIST {position: 2}]->(l);
Cypher statement to move an item by its id
property and 'swap' it with another
item with id c
. Here, 'swap' doesn't mean the literal meaning of swap, which
would be to exchange the indices of the appropriate items. Moving an item
to an item that is currently before it means that it gets moved before that
item, while moving an item to an item after it means that it gets moved after
that item. The net effect is a full range of movement possibilities.
MATCH
(l:List {id: 42}),
(i1:Item {id: "a"})-[r1:IN_LIST]->(l),
(i2:Item {id: "c"})-[r2:IN_LIST]->(l)
WITH
r1.position AS oldPosition,
r2.position AS newPosition,
r1 AS r1,
CASE
WHEN r2.position < r1.position THEN 1
WHEN r2.position > r1.position THEN -1
ELSE 0
END AS signum
MATCH (i:Item)-[r3:IN_LIST]->(:List {name: "Stuff"})
WHERE
(newPosition < oldPosition
AND r3.position >= newPosition AND r3.position < oldPosition)
OR (newPosition > oldPosition
AND r3.position > oldPosition AND r3.position <= newPosition)
SET r2.position = r2.position + signum, r1.position = newPosition;
Update December 2020: This is subtle enough that I thought that it merited its own repository with a test suite. See the implementation for more details on how this works. BTW I don't make any performance claims about this code.