cancel
Showing results for 
Search instead for 
Did you mean: 

UNWIND with js driver yields "Cannot merge node using null property ..."

ekobi
Node Link

Hi,

Running into a brick wall using the javascript driver to migrate JSON data into Neo4j.

Simple transaction wrapper:

const n4jTxn = ({ neo4jDriver, unitsOfWork }) => {
  const session = neo4jDriver.session({ defaultAccessMode: neo4j.session.WRITE});
  return session.writeTransaction((txn) => {
    const promises = _.map(unitsOfWork, (uow) => {
      console.log(`[n4jTxn] unitOfWork: ${JSON.stringify(uow, {}, 2)}`);
      return txn.run({ text: uow.query, parameters: uow.parameters});
    });
    return Promise.all(promises);
  }).then((results) => {
    console.log(`[n4jTxn] result: ${JSON.stringify(results, {}, 2)}`);
    session.close();
    return results;
  }).catch((error) => {
    session.close();
    throw error;
  });

};

Yields the dreaded "Cannot merge node using null property value for xyz..."

[n4jTxn] unitOfWork: {
  "query": "UNWIND $users AS u MERGE (newUser:User {  _id: u._id, name: u.name, email: u.email, accountId: u.accountId }) RETURN newUser",
  "parameters": {
    "users": [
      {
        "_id": "5cc1efebe13133250e8bc9c6",
        "name": "Admin 1",
        "email": "admin1@example.com",
        "accountId": "de34d0a8-4715-40aa-8cc5-dasdfjad"
      },
      {
        "_id": "5cc1efebe13133250e8bc9c9",
        "name": "GC 40",
        "email": "gc40@examplecom",
        "accountId": "b70f5117-4f1f-45a9-8278-addkadk"
      },
      {
        "_id": "5cc1efebe13133250e8bc9cb",
        "name": "Architect 1",
        "email": "architect1@example.com",
        "accountId": "3937f941-040d-40ed-b1ee-alsdfa"
      }
    ]
  }
}
[migrate -- migrateUsers] Neo4jError: Cannot merge node using null property value for accountId.

Any thoughts on what's going on? Thanks.

1 ACCEPTED SOLUTION

ekobi
Node Link

For the record, the issue seems to be that the js driver is not correctly serializing the parameters. Re-implementing the transaction wrapper to use the HTTP interface works as expected:

const urllib = require('urllib');
const txnURL= "http://localhost:7474/db/neo4j/tx/commit";
const n4jTxn = async (statements) => {
  //
  // expect statements to be an array of statement + query dicts:
  //
  //     statements: [{statement: query, parameters: params }] ;
  const data = { statements };
  // console.log(`[n4jTxn] payload data: ${JSON.stringify(data, {}, 2)}`);
  return urllib.request(txnURL, {dataType: 'json', contentType: 'json', auth: AUTH_STRING, method: 'POST', data })
    .catch((error) => {
      console.error(`[n4jTxn] error: ${JSON.stringify(error, {}, 2)}`);
      throw error;
    })
    .then((results) => {
      console.log(`[n4jTxn] results: ${JSON.stringify(results, {}, 2)}`);
      return results;
    });
};

View solution in original post

3 REPLIES 3

david_allen
Neo4j
Neo4j

Specifically -- you don't have any xyz properties in there. It's not clear if you meant that as a generic example or not, because no xyz properties are mentioned in your code.

More generally -- MERGE is a combination of MATCH and CREATE. MERGE needs to first try to find nodes by a particular property set, so you can't use null property values with merge because of special details about how null operates. For example this won't work:

MERGE (f:Foo { x: null })

So the answer is to migrate any properties that might be null outside of the MERGE clause. Like this:

MERGE (f:Foo { id: 1 })
  SET f.otherProp = $param

This will work even if $param is null, where MERGE (f:Foo { id: 1, otherProp: $param }) would fail.

The second code block shows the parameters getting passed on the wire, in addition to the query itself. I'm trying to apply this pattern to my data: Creating Nodes from a list parameter.

ekobi
Node Link

For the record, the issue seems to be that the js driver is not correctly serializing the parameters. Re-implementing the transaction wrapper to use the HTTP interface works as expected:

const urllib = require('urllib');
const txnURL= "http://localhost:7474/db/neo4j/tx/commit";
const n4jTxn = async (statements) => {
  //
  // expect statements to be an array of statement + query dicts:
  //
  //     statements: [{statement: query, parameters: params }] ;
  const data = { statements };
  // console.log(`[n4jTxn] payload data: ${JSON.stringify(data, {}, 2)}`);
  return urllib.request(txnURL, {dataType: 'json', contentType: 'json', auth: AUTH_STRING, method: 'POST', data })
    .catch((error) => {
      console.error(`[n4jTxn] error: ${JSON.stringify(error, {}, 2)}`);
      throw error;
    })
    .then((results) => {
      console.log(`[n4jTxn] results: ${JSON.stringify(results, {}, 2)}`);
      return results;
    });
};