Neo4jError: Unable to pack the given value: - Invalid input 'MERGE': expected "OPTIONS" or <EOF> error depending on the parameter passed to the cypher

Viewed 24

I'm starting implementing Neo4j in my Node.js project where I first write to MongoDB within a transaction and then write to Neo4j which should fails aborts the transaction so consistency between the two is ensured. I'm getting two different errors when executing the cypher depending if I pass in a JS Object or direct parameters. If I pass the alert object and try to read its parameters I get this error:

Neo4jError: Unable to pack the given value: function() {
    let states = [...arguments];
    const callback = states.pop();

    if (!states.length) states = this.stateNames;

    const _this = this;

    const paths = states.reduce(function(paths, state) {
      if (_this.states[state] == null) {
        return paths;
      }
      return paths.concat(Object.keys(_this.states[state]));
    }, []);

    return paths[iterMethod](function(path, i, paths) {
      return callback(path, i, paths);
    });
  }

    at captureStacktrace (/Users/vincenzocalia/server-node/node_modules/neo4j-driver-core/lib/result.js:611:17)
    at new Result (/Users/vincenzocalia/server-node/node_modules/neo4j-driver-core/lib/result.js:105:23)
    at newCompletedResult (/Users/vincenzocalia/server-node/node_modules/neo4j-driver-core/lib/transaction.js:501:12)
    at Object.run (/Users/vincenzocalia/server-node/node_modules/neo4j-driver-core/lib/transaction.js:333:20)
    at Transaction.run (/Users/vincenzocalia/server-node/node_modules/neo4j-driver-core/lib/transaction.js:174:34)
    at ManagedTransaction.run (/Users/vincenzocalia/server-node/node_modules/neo4j-driver-core/lib/transaction-managed.js:56:21)
    at /Users/vincenzocalia/server-node/api/src/neo4j/alert_neo4j.js:22:53
    at TransactionExecutor._safeExecuteTransactionWork (/Users/vincenzocalia/server-node/node_modules/neo4j-driver-core/lib/internal/transaction-executor.js:141:26)
    at TransactionExecutor.<anonymous> (/Users/vincenzocalia/server-node/node_modules/neo4j-driver-core/lib/internal/transaction-executor.js:128:46)
    at step (/Users/vincenzocalia/server-node/node_modules/neo4j-driver-core/lib/internal/transaction-executor.js:52:23) {
  constructor: [Function: Neo4jError] { isRetriable: [Function (anonymous)] },
  code: 'ProtocolError',
  retriable: false
}

so I guess that there is no mapping for the JS object. If I instead pass the object's values I get a Merge error:

Neo4jError: Invalid input 'MERGE': expected "OPTIONS" or <EOF> (line 3, column 13 (offset: 83))
"      MERGE (u:User {id: $userId})"
       ^

    at captureStacktrace (/Users/vincenzocalia/server-node/node_modules/neo4j-driver-core/lib/result.js:611:17)
    at new Result (/Users/vincenzocalia/server-node/node_modules/neo4j-driver-core/lib/result.js:105:23)
    at newCompletedResult (/Users/vincenzocalia/server-node/node_modules/neo4j-driver-core/lib/transaction.js:501:12)
    at Object.run (/Users/vincenzocalia/server-node/node_modules/neo4j-driver-core/lib/transaction.js:333:20)
    at Transaction.run (/Users/vincenzocalia/server-node/node_modules/neo4j-driver-core/lib/transaction.js:174:34)
    at ManagedTransaction.run (/Users/vincenzocalia/server-node/node_modules/neo4j-driver-core/lib/transaction-managed.js:56:21)
    at /Users/vincenzocalia/server-node/api/src/neo4j/alert_neo4j.js:22:53
    at TransactionExecutor._safeExecuteTransactionWork (/Users/vincenzocalia/server-node/node_modules/neo4j-driver-core/lib/internal/transaction-executor.js:141:26)
    at TransactionExecutor.<anonymous> (/Users/vincenzocalia/server-node/node_modules/neo4j-driver-core/lib/internal/transaction-executor.js:128:46)
    at step (/Users/vincenzocalia/server-node/node_modules/neo4j-driver-core/lib/internal/transaction-executor.js:52:23) {
  constructor: [Function: Neo4jError] { isRetriable: [Function (anonymous)] },
  code: 'Neo.ClientError.Statement.SyntaxError',
  retriable: false
}

As I understood it Merge should create a node if it doesn't exists otherwise will match a node, in my cypher I set the :Alert .id property as unique, then I get a :User node if exists or create it if it doesn't, create a :Alert node, create the :POSTED_ALERT relationship and return the alert so I can check that it was created. Can you spot what I'm doing wrong?

exports.saveAlert = async (alert) => {

    console.log('neoAlert.saveAlert alert:', alert);
    const driver = neo4j.getDriver();
    const session = driver.session();
    const userId = alert.userId;
    const alertId = alert.id;
    const res = await session.executeWrite(tx => tx.run(
      // `
      // CREATE CONSTRAINT alert_id ON (alert:Alert) ASSERT alert.id IS UNIQUE
      // MERGE (u:User {id: $alert.userId})-[r:POSTED_ALERT]->(a:Alert{id: $alert.id})
      
      `
      CREATE CONSTRAINT alert_id ON (alert:Alert) ASSERT alert.id IS UNIQUE
      MERGE (u:User {id: $userId})
      MERGE (a:Alert {id: $alertId})
      MERGE (u)-[r:POSTED_ALERT]->(a)
      RETURN a AS alert
      `
      ,
      {alert ,alertId, userId}
    ));
    await session.close();
    if ( res.records.length === 0 ) {
      throw new NotFoundError(`Neo4j could not create record for Alert ${alert.id} by User ${alert.userId}`);
    }

    console.log(`Neo4j saved ${res.records.length} alerts`);
    const alerts = res.records;
    const saved = alerts.length('alert');
    console.log('Neo4j saved alert is: ', saved);
    return saved;
    // throw Error; 
  }
1 Answers

Found the problem. It seems that creating a constraint within the same cypher was what messed the transaction, so I put it in its own method. Also as the JS object parameters are not available inside the cypher I just map them to variables when passing parameters to the cypher. Hope this will help others.

exports.saveAlert = async (alert) => {

    console.log('neoAlert.saveAlert alert:', alert);
    const driver = neo4j.getDriver();
    const session = driver.session();
    const userId = alert.userId;
    const alertId = alert.id;
    const constraint  = await session.run('CREATE CONSTRAINT alert_id IF NOT EXISTS FOR (alert:Alert) REQUIRE alert.id IS UNIQUE');

    const res = await session.executeWrite(tx => tx.run(
      `
      MERGE (u:User {id: $userId})
      MERGE (a:Alert {id: "$alertId"})
      SET 
      a.city = $city, 
      a.region = $region,
      a.country = $country,
      a.date = $date,
      a.latitude = $latitude,
      a.longitude = $longitude,
      a.description = $description,
      a.alertIcon = $alertIcon,
      a.alertImageUrl = $alertImageUrl,
      a.alertImageName = $alertImageName,
      a.userId = $userId,
      a.userName = $userName,
      a.fcmToken = $fcmToken,
      a.utilityPoints = $utilityPoints,
      a.votesToDelete = $votesToDelete
      MERGE (u)-[r:POSTED_ALERT]->(a)
      RETURN a AS alert
      `
      ,
      {
        alertId: alert.id, 
        userId: alert.userId,
        city: alert.city,
        region: alert.region,
        country: alert.country,
        date: alert.date,
        latitude: alert.latitude,
        longitude: alert.longitude,
        description: alert.description,
        alertIcon: alert.alertIcon,
        alertImageUrl: alert.alertImageUrl,
        alertImageName: alert.alertImageName,
        userName: alert.userName,
        fcmToken: alert.fcmToken,
        utilityPoints: alert.utilityPoints,
        votesToDelete: alert.votesToDelete     
      }
    ));
    await session.close();
    if ( res.records.length === 0 ) {
      throw new NotFoundError(`Neo4j could not create record for Alert ${alert.id} by User ${alert.userId}`);
    }

    console.log(`Neo4j saved ${res.records.length} alerts`);
    const alerts = res.records;
    const saved = alerts[0].get('alert');
    console.log('Neo4j saved alert is: ', saved);
    return saved;
  }
Related