Neo4j driver driving me crazy - Strange behaviour


(Alain Couniot) #1

I'm trying to load Enterprise Architecture-related data into Neo4j via a small Node.js (TypeScript) application reading an XLSX file and I'm experiencing very a very strange behaviour of - I suspect - the neo4j-driver (v. 1.7.2) for MERGE statements (only using strings, nothing more sophisticated :-( ).

To put it briefly:

  • MERGE statements don't work as expected via the driver (duplicate nodes are created) whereas the same instructions (except that they use constants instead of parameters) work as expected from the browser

  • If I add UNIQUE constraints to avoid duplicates, no duplicates are created (OK) but no error is returned by the driver (violations of the constraints are totally silent!)

I suspect a vicious bug somewhere because while preparing a reduced example (to open an issue on the driver's site on GitHub), I used a first simple dataset ( ['t1', 't1', 't2', 't1', 't1', 't1', 't2', 't1']) and was initially unable to reproduce the problem, then I added some real data to the first dataset and got the problem (of duplicates), and finally went back to the first dataset above and had the problem again!!.

I got exactly the same outcome using a Neo4j 3.5.2 container running on a Synology and using Neo4j 3.4.0 running as an installed application on my Ubuntu 18.04LTS workstation (both with the same TypeScript code and the same driver.

Below my TypeScript code and the related package.json. (The chosen approach might look a bit too involved but it is due partly to the complexity of the actual code - trimmed down here - and partly to my attempts to identify the source of the problem [I initially suspected some concurrency problems in my application, having several Promises running in parallel; so I tried to work purely sequentially but it didn't solve anything, hence my suspicion there is a problem with the driver).

I think my understanding of MERGE is correct as manual interaction with Neo4j through the browser gives the expected result (but not application interaction through neo4j-driver).

============= Sample application (aris-load-github.ts) ==========

// Sample code aimed at illustrating the problem for reporting on the GitHub site

import { of, from } from 'rxjs';
import { map, take } from 'rxjs/operators';

var neo4j = require('neo4j-driver').v1;

const driver = neo4j.driver('bolt://localhost:7687/',
neo4j.auth.basic('neo4j', '********'));

// ===========================================================================

const qry_ProcessType = MERGE (t: ari_ProcessType { Name: {Type} } ) return t;;

async function runQuery(qry: string, parms: any, res: string): Promise<void> {

return new Promise<void>((resolve, reject) => {
    var session = driver.session();

    session
        .run(qry, parms)
        .then((res: any) => { session.close(); resolve(undefined); })
        .catch((err: Error) => { session.close(); reject(err); });
});

}

async function checkProcessType(t: string): Promise {
if (t && t.length > 0) {
console.log(checkProcessType: ${t});
await runQuery(qry_ProcessType, {
Type: t
}, 't');
}
return new Promise((resolve, reject) => { resolve(t); });
}

const tlist = ['t1', 't1', 't2', 't1', 't1', 't1', 't2', 't1',
'L4 - Process (Flowchart)',
'L4 - Process (Flowchart)',
'L4 - Process (Flowchart)',
'L4 - Process (Flowchart)',
'L4 - Process (Flowchart)',
'L4 - Process (Flowchart)',
'L4 - Process (Flowchart)',
'L4 - Process (Flowchart)'
/*
"L4 - Process (Flowchart)",
"L4 - Process (Flowchart)",
"L4 - Process (Flowchart)",
"L4 - Process (Flowchart)",
"L4 - Process (Flowchart)",
"L4 - Process (Flowchart)",
"L4 - Process (Flowchart)",
"L4 - Process (Flowchart)"
*/
];

const rows$ = from(tlist).subscribe(
r => {
checkProcessType(r);
}
);

=========== Corresponding package.json ===============
{
"name": "yoga-aramis",
"version": "1.0.0",
"description": "Yoga-based GraphQL interface for Neo4j",
"main": "index.js",
"scripts": {
"test": "echo "Error: no test specified" && exit 1"
},
"keywords": [
"GraphQL",
"Neo4j"
],
"author": "Alain Couniot",
"license": "ISC",
"dependencies": {
"@log4js2/core": "^2.0.6",
"graphql-yoga": "^1.17.1",
"neo4j-driver": "^1.7.2",
"rxjs": "^6.4.0",
"sprintf-js": "^1.1.2",
"xlsx-extractor": "^1.2.2"
},
"devDependencies": {
"@types/sprintf-js": "^1.1.1",
"nodemon": "^1.18.9"
}
}

============= Application output (no error! even when unique constraint is added) =========

$ tsc --lib esnext src/aris-load-github && node src/aris-load-github
checkProcessType: t1
checkProcessType: t1
checkProcessType: t2
checkProcessType: t1
checkProcessType: t1
checkProcessType: t1
checkProcessType: t2
checkProcessType: t1
checkProcessType: L4 - Process (Flowchart)
checkProcessType: L4 - Process (Flowchart)
checkProcessType: L4 - Process (Flowchart)
checkProcessType: L4 - Process (Flowchart)
checkProcessType: L4 - Process (Flowchart)
checkProcessType: L4 - Process (Flowchart)
checkProcessType: L4 - Process (Flowchart)
checkProcessType: L4 - Process (Flowchart)

================================================
Result of "match(n:ari_ProcessType) return n" in the neo4j browser:
╒═══════════════════════════════════╕
│"n" │
╞═══════════════════════════════════╡
│{"Name":"L4 - Process (Flowchart)"}│
├───────────────────────────────────┤
│{"Name":"t1"} │
├───────────────────────────────────┤
│{"Name":"t2"} │
├───────────────────────────────────┤
│{"Name":"t1"} │
├───────────────────────────────────┤
│{"Name":"L4 - Process (Flowchart)"}│
├───────────────────────────────────┤
│{"Name":"L4 - Process (Flowchart)"}│
├───────────────────────────────────┤
│{"Name":"L4 - Process (Flowchart)"}│
├───────────────────────────────────┤
│{"Name":"t1"} │
├───────────────────────────────────┤
│{"Name":"t2"} │
├───────────────────────────────────┤
│{"Name":"L4 - Process (Flowchart)"}│
├───────────────────────────────────┤
│{"Name":"L4 - Process (Flowchart)"}│
├───────────────────────────────────┤
│{"Name":"t1"} │
├───────────────────────────────────┤
│{"Name":"L4 - Process (Flowchart)"}│
├───────────────────────────────────┤
│{"Name":"t1"} │
├───────────────────────────────────┤
│{"Name":"t1"} │
└───────────────────────────────────┘

Note: there seems to be 1 "Flowchart" missing

Any hint welcome! ;-)