Search code examples
javascriptneo4jcypherneo4j-driver

Nested FOREACH on array only executes on first array value


I am writing a function to seed my database data. The first thing that I do is clearing everything from the database:

export const clearDb = async () => {
  const deleteQuery = `
    MATCH (n)
    DETACH DELETE n
    `;
  await session.writeTransaction((tx) => tx.run(deleteQuery));
  console.log("done clearing DB!");
};

My data is in the following format, save as a JavaScript variable:

type Temperature = "warm";

interface SeedData {
  alternativeNames: string[];
  name: string;
  hex: string;
  tags: string[];
  temperature: Temperature;
}

export const INITIAL_DATA: SeedData[] = [
  {
    name: "Butter",
    alternativeNames: ["Light Yellow", "Cream", "Lemon", "Daffodil", "Vanilla"],
    hex: "#ffff80",
    tags: [
      "cheerful",
      "pleasant",
      "gentle",
      "happy",
      "optimistic",
      "soft",
      "institutional",
    ],
    temperature: "warm",
  }
]

For every object inside my initial data, I would like the query to:

  • create a :Color node with the properties hex, name, and temperature as string, and alternativeNames as a list
  • create a :Tag node for each string inside the tags array, with a propety name that saves the value of the string, and a relationship of a type ASSOCIATED_WITH from the created :Color node to the created :Tag node

This is my query and the function that calls it:

export const seedColors = async () => {
  const writeQuery = `
    FOREACH (color in $colors |
        MERGE (c:Color {hex: color.hex})
            ON CREATE 
                SET c.hex = color.hex  
                SET c.name = color.name
                SET c.alternativeNames = color.alternativeNames
                SET c.temperature = color.temperature
            FOREACH (tag in color.tags | 
                MERGE(c)-[a:ASSOCIATED_WITH]->(t:Tag)
                    ON CREATE 
                        SET t.name = tag 
                ))
    RETURN $colors AS colors
    `;
  await session.writeTransaction((tx) =>
    tx.run(writeQuery, { colors: INITIAL_DATA })
  );
  console.log("done seeding data!");
};

I then call everything using this function:

export const seedData = async () => {
  try {
    await clearDb();
    await seedColors();
  } catch (e) {
    console.log(e);
    throw e;
  } finally {
    session.close();
  }
};

The end result is that all the :Color nodes from my data get created inside the DB, but each of them only has a single :Tag with a ASSOCIATED_WITH relationship. Only a single :Tag is created per :Color, while in the example given I would expect 7 tags and 7 relationships.

What am I doing wrong?


Solution

  • I managed to get the desired result with two UNWIND statements, but I still do not know why the initial query did not work:

    const writeQuery = `
        UNWIND $colors as color 
            MERGE (c:Color {hex: color.hex})
                ON CREATE 
                    SET c.hex = color.hex  
                    SET c.name = color.name
                    SET c.alternativeNames = color.alternativeNames
                    SET c.temperature = color.temperature
        WITH color, c UNWIND color.tags as tag 
                    MERGE(c)-[a:ASSOCIATED_WITH]->(:Tag {name: tag})
        RETURN $colors AS colors
        `;