diff --git a/package-lock.json b/package-lock.json index c24a40e..1177ca4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "Neo4j", - "version": "0.2.3", + "version": "0.2.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "Neo4j", - "version": "0.2.3", + "version": "0.2.4", "hasInstallScript": true, "dependencies": { "async": "3.2.6", @@ -26,6 +26,7 @@ "eslint-plugin-prettier": "5.1.3", "eslint-plugin-unused-imports": "3.2.0", "lint-staged": "14.0.1", + "patch-package": "8.0.0", "prettier": "3.2.5", "simple-git-hooks": "2.11.1" }, @@ -784,6 +785,12 @@ "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", "dev": true }, + "node_modules/@yarnpkg/lockfile": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", + "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", + "dev": true + }, "node_modules/acorn": { "version": "8.11.3", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", @@ -1007,6 +1014,15 @@ "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==" }, + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, "node_modules/available-typed-arrays": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", @@ -1136,6 +1152,21 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "engines": { + "node": ">=8" + } + }, "node_modules/clean-stack": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", @@ -2065,6 +2096,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/find-yarn-workspace-root": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz", + "integrity": "sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==", + "dev": true, + "dependencies": { + "micromatch": "^4.0.2" + } + }, "node_modules/flat-cache": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", @@ -2599,6 +2639,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -2786,6 +2841,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/isarray": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", @@ -2822,6 +2889,24 @@ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, + "node_modules/json-stable-stringify": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.1.1.tgz", + "integrity": "sha512-SU/971Kt5qVQfJpyDveVhQ/vya+5hvrjClFOcr8c0Fq5aODJjMwutrOfCU+eCnVD5gpx1Q3fEqkyom77zH1iIg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "isarray": "^2.0.5", + "jsonify": "^0.0.1", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", @@ -2852,6 +2937,15 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/jsonify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.1.tgz", + "integrity": "sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -2861,6 +2955,15 @@ "json-buffer": "3.0.1" } }, + "node_modules/klaw-sync": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz", + "integrity": "sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.11" + } + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -3254,6 +3357,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/open": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", + "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", + "dev": true, + "dependencies": { + "is-docker": "^2.0.0", + "is-wsl": "^2.1.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -3271,6 +3390,15 @@ "node": ">= 0.8.0" } }, + "node_modules/os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -3328,6 +3456,73 @@ "node": ">=6" } }, + "node_modules/patch-package": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/patch-package/-/patch-package-8.0.0.tgz", + "integrity": "sha512-da8BVIhzjtgScwDJ2TtKsfT5JFWz1hYoBl9rUQ1f38MC2HwnEIkK8VN3dKMKcP7P7bvvgzNDbfNHtx3MsQb5vA==", + "dev": true, + "dependencies": { + "@yarnpkg/lockfile": "^1.1.0", + "chalk": "^4.1.2", + "ci-info": "^3.7.0", + "cross-spawn": "^7.0.3", + "find-yarn-workspace-root": "^2.0.0", + "fs-extra": "^9.0.0", + "json-stable-stringify": "^1.0.2", + "klaw-sync": "^6.0.0", + "minimist": "^1.2.6", + "open": "^7.4.2", + "rimraf": "^2.6.3", + "semver": "^7.5.3", + "slash": "^2.0.0", + "tmp": "^0.0.33", + "yaml": "^2.2.2" + }, + "bin": { + "patch-package": "index.js" + }, + "engines": { + "node": ">=14", + "npm": ">5" + } + }, + "node_modules/patch-package/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/patch-package/node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/patch-package/node_modules/slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -4019,6 +4214,18 @@ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, + "node_modules/tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "dependencies": { + "os-tmpdir": "~1.0.2" + }, + "engines": { + "node": ">=0.6.0" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", diff --git a/package.json b/package.json index e173f58..89a3da1 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,7 @@ "scripts": { "lint": "eslint . --max-warnings=0", "package": "node esbuild.package.js", - "postinstall": "npx patch-package" + "postinstall": "patch-package" }, "devDependencies": { "@hackolade/hck-esbuild-plugins-pack": "0.0.1", @@ -78,7 +78,8 @@ "eslint-plugin-prettier": "5.1.3", "eslint-plugin-unused-imports": "3.2.0", "lint-staged": "14.0.1", + "patch-package": "8.0.0", "prettier": "3.2.5", "simple-git-hooks": "2.11.1" } -} \ No newline at end of file +} diff --git a/reverse_engineering/api.js b/reverse_engineering/api.js index f078fe5..5a3e57a 100644 --- a/reverse_engineering/api.js +++ b/reverse_engineering/api.js @@ -146,7 +146,7 @@ module.exports = { const isMultiDb = await neo4j.supportsMultiDb(); const dbVersion = await neo4j.getDbVersion(); - logger.log('info', `Database version: ${dbVersion}`); + logger.log('info', `Version: ${dbVersion}`, 'Database info'); const modelProps = { dbVersion, }; @@ -166,7 +166,7 @@ module.exports = { metaData.indexes = modelProps.dbVersion === '3.x' ? prepareIndexes3x(indexes) : prepareIndexes4x(indexes); - const countIndexes = (indexes && indexes.length) || 0; + const countIndexes = indexes?.length || 0; logger.progress({ message: 'Indexes retrieved successfully. Found ' + countIndexes + ' index(es)', containerName: dbName, @@ -184,7 +184,7 @@ module.exports = { .then(constraints => { metaData.constraints = prepareConstraints(constraints, modelProps.dbVersion); - const countConstraints = (constraints && constraints.length) || 0; + const countConstraints = constraints?.length || 0; logger.progress({ message: 'Constraints retrieved successfully ' + countConstraints + ' constraint(s)', containerName: dbName, @@ -195,19 +195,19 @@ module.exports = { return metaData; }) .then(metaData => { - return getNodesData( + return getNodesData({ dbName, labels, isMultiDb, - { + data: { recordSamplingSettings, fieldInference, includeEmptyCollection, indexes: metaData.indexes, constraints: metaData.constraints, }, - (entityName, message) => logger.progress({ message, containerName: dbName, entityName }), - ); + logger, + }); }) .then(labelPackages => { packages.labels.push(labelPackages); @@ -223,7 +223,7 @@ module.exports = { }); logger.log('info', dbName, 'Start retrieving schema'); - return neo4j.getSchema(dbName, labels, isMultiDb); + return neo4j.getSchema({ dbName, labels, isMultiDb, logger }); }) .then(schema => { logger.progress({ @@ -276,7 +276,7 @@ module.exports = { logger.log('info', '', 'Reverse-engineering completed'); setTimeout(() => { - cb(err, packages.labels, modelProps, [].concat.apply([], packages.relationships)); + cb(err, packages.labels, modelProps, [].concat(...packages.relationships)); }, 1000); }, ); @@ -324,49 +324,47 @@ const logInfo = (step, connectionInfo, logger) => { logger.log('info', connectionInfo, 'connectionInfo', connectionInfo.hiddenKeys); }; -const getNodesData = (dbName, labels, isMultiDb, data, logger) => { +const getNodesData = ({ dbName, labels, isMultiDb, data, logger }) => { + const logProgress = (entityName, message) => { + const step = `Preparing package for "${entityName}"`; + + logger.progress({ message, containerName: dbName, entityName }); + logger.log('info', message, step); + }; return new Promise((resolve, reject) => { let packages = []; - async.map( - labels, - (labelName, nextLabel) => { - logger(labelName, 'Getting data...'); + let labelPromises = labels.map(async label => { + logProgress(label, 'Getting data...'); - neo4j - .getNodesCount(labelName, dbName, isMultiDb) - .then(quantity => { - const count = getSampleDocSize(quantity, data.recordSamplingSettings); - logger(labelName, 'Found ' + count + ' nodes'); + const quantity = await neo4j.getNodesCount(label, dbName, isMultiDb); + const count = getSampleDocSize(quantity, data.recordSamplingSettings); - return neo4j.getNodes(labelName, count, dbName, isMultiDb); - }) - .then(documents => { - logger(labelName, 'Data retrieved successfully'); + logProgress(label, `Found ${count} nodes.`); - const packageData = getLabelPackage( - dbName, - labelName, - documents, - data.includeEmptyCollection, - data.fieldInference, - data.indexes[labelName], - getConstraintsForEntity(labelName, 'node', data.constraints), - ); - if (packageData) { - packages.push(packageData); - } - nextLabel(null); - }) - .catch(nextLabel); - }, - err => { - if (err) { - reject(err); - } else { - resolve(packages); - } - }, - ); + const documents = await neo4j.getNodes({ label, limit: count, dbName, isMultiDb }); + + logProgress(label, 'Nodes data retrieved successfully. Preparing packages...'); + + const packageData = getLabelPackage( + dbName, + label, + documents, + data.includeEmptyCollection, + data.fieldInference, + data.indexes[label], + getConstraintsForEntity(label, 'node', data.constraints), + ); + if (packageData) { + logProgress(label, 'Package prepared.'); + packages.push(packageData); + } else { + logProgress(label, 'The package data is empty.'); + } + }); + + Promise.all(labelPromises) + .then(() => resolve(packages)) + .catch(err => reject(err)); }); }; @@ -388,14 +386,14 @@ const getRelationshipData = ( .getCountRelationshipsData(chain.start, chain.relationship, chain.end, dbName, isMultiDb) .then(quantity => { const count = getSampleDocSize(quantity, recordSamplingSettings); - return neo4j.getRelationshipData( - chain.start, - chain.relationship, - chain.end, + return neo4j.getRelationshipData({ + start: chain.start, + relationship: chain.relationship, + end: chain.end, count, dbName, isMultiDb, - ); + }); }) .then(rawDocuments => { const documents = deserializeData(rawDocuments); @@ -481,10 +479,10 @@ const getLabelPackage = ( }; const prepareIndexes3x = indexes => { - const hasProperties = /INDEX\s+ON\s+\:(.*)\((.*)\)/i; + const hasProperties = /INDEX\s+ON\s+:(.*)\((.*)\)/i; const map = {}; - indexes.forEach((index, i) => { + indexes.forEach(index => { if (!index.properties) { if (hasProperties.test(index.description)) { const parsedDescription = index.description.match(hasProperties); @@ -527,7 +525,7 @@ const prepareIndexes4x = indexes => { provider: index.provider || index.indexProvider, }; - index.labelsOrTypes.forEach((label, i) => { + index.labelsOrTypes.forEach(label => { if (!map[label]) { map[label] = [index_obj]; } else { @@ -588,10 +586,10 @@ const prepareConstraints5x = constraints => { }; const prepareConstraints4x = constraints => { - const isUnique = /^constraint\s+on\s+\([\s\S]+\:([\S\s]+)\s*\)\s+assert\s+[\s\S]+\.([\s\S]+)\s*\)\s+IS\s+UNIQUE/i; + const isUnique = /^constraint\s+on\s+\([\s\S]+:([\S\s]+)\s*\)\s+assert\s+[\s\S]+\.([\s\S]+)\s*\)\s+IS\s+UNIQUE/i; const isNodeKey = - /^constraint\s+on\s+\([\s\S]+\:\s*([\S\s]+)\s*\)\s+assert\s+(?:\(\s*([\s\S]+)\s*\)|[\s\S]+\.\s*([\S\s]+)\s*)\s+IS\s+NODE\s+KEY/i; - const isExists = /^constraint\s+on\s+\([\s\S]+\:([\s\S]+)\s*\)\s+assert\s+exists\([\s\S]+\.([\s\S]+)\s*\)/i; + /^constraint\s+on\s+\([\s\S]+:\s*([\S\s]+)\s*\)\s+assert\s+(?:\(\s*([\s\S]+)\s*\)|[\s\S]+\.\s*([\S\s]+)\s*)\s+IS\s+NODE\s+KEY/i; + const isExists = /^constraint\s+on\s+\([\s\S]+:([\s\S]+)\s*\)\s+assert\s+exists\([\s\S]+\.([\s\S]+)\s*\)/i; let result = { nodeAndRelationship: {} }; const addToResult = (result, name, label, key, type, keyName = 'key') => { const labelName = label.trim(); @@ -819,7 +817,7 @@ const getSnippetPropertiesByName = name => { const properties = {}; snippet.properties.forEach(fieldSchema => { - properties[fieldSchema.name] = Object.assign({}, fieldSchema); + properties[fieldSchema.name] = { ...fieldSchema }; delete properties[fieldSchema.name].name; }); @@ -829,7 +827,7 @@ const getSnippetPropertiesByName = name => { const separateConstraintsByType = (constraints = []) => { return constraints.reduce( (result, constraint) => { - constraint = Object.assign({}, constraint); + constraint = { ...constraint }; const type = constraint.type; delete constraint.type; result[type].push(constraint); diff --git a/reverse_engineering/neo4jHelper.js b/reverse_engineering/neo4jHelper.js index b4db0b2..3f257d9 100644 --- a/reverse_engineering/neo4jHelper.js +++ b/reverse_engineering/neo4jHelper.js @@ -6,9 +6,10 @@ let driver; let isSshTunnel = false; const EXECUTE_TIME_OUT_CODE = 'EXECUTE_TIME_OUT'; -let timeout; +const defaultTimeout = 300000; +let timeout = defaultTimeout; -const setTimeOut = data => (timeout = data?.queryRequestTimeout || 300000); +const setTimeOut = data => (timeout = data?.queryRequestTimeout || defaultTimeout); const connectToInstance = (info, checkConnection) => { return checkConnection(info.host, info.port).then( @@ -84,7 +85,7 @@ const execute = async (command, database = undefined, isMultiDb = false) => { database = undefined; } const executeTimeout = new Promise((_, reject) => - setTimeout(() => reject(getExecuteTimeoutError(timeout)), timeout), + setTimeout(() => reject(getExecuteTimeoutError({ timeout, command })), timeout), ); let result = []; let db; @@ -116,8 +117,8 @@ const execute = async (command, database = undefined, isMultiDb = false) => { } }; -const getExecuteTimeoutError = timeout => { - const error = new Error(`execute timeout: ${timeout}ms exceeded`); +const getExecuteTimeoutError = ({ timeout, command }) => { + const error = new Error(`Execute timeout: ${timeout}ms exceeded. Query:\n${command}`); error.code = EXECUTE_TIME_OUT_CODE; return error; }; @@ -137,16 +138,27 @@ const castInteger = properties => { }; const getLabels = async ({ database, isMultiDb, logger }) => { + const step = 'Retrieving labels information'; try { + const labelsMeta = new Map(); + + const allLabels = await execute('CALL db.labels()', database, isMultiDb); + allLabels.forEach(({ label }) => labelsMeta.set(label, 0)); + const recordsCounter = await execute( - 'MATCH (n) RETURN DISTINCT COUNT(labels(n)) as labelsCount', + 'MATCH (n) RETURN DISTINCT labels(n) as label, COUNT(*) as total ORDER BY total DESC', database, isMultiDb, ); - logger.log('info', `Found ${_.head(recordsCounter).labelsCount} labels`, 'Retrieving labels information'); + recordsCounter.forEach(record => { + const label = _.head(record.label); + labelsMeta.set(label, record.total); + }); + + const labelsToLog = [...labelsMeta.entries()].map(([label, total]) => `${label} (${total})`).join('\n'); + logger.log('info', `Found ${labelsMeta.size} unique labels:\n${labelsToLog}`, step); - const records = await execute('MATCH (n) RETURN DISTINCT labels(n) as label', database, isMultiDb); - return _.flatMap(records, record => record.label); + return [...labelsMeta.keys()]; } catch (error) { const errorStep = error.step || 'Error of retrieving labels'; logger.log('error', error, errorStep); @@ -154,7 +166,7 @@ const getLabels = async ({ database, isMultiDb, logger }) => { } }; -const getSchema = (dbName, labels, isMultiDb) => { +const getSchema = ({ dbName, labels, isMultiDb }) => { return execute(`call apoc.meta.subGraph({labels: ["${labels.join('","')}]})`, dbName, isMultiDb) .then( result => result, @@ -223,13 +235,13 @@ const getActiveMultiDbsNames = async () => { .map(({ name }) => name); }; -const getNodes = (label, limit = 100, dbName, isMultiDb) => { +const getNodes = ({ label, limit = 100, dbName, isMultiDb }) => { return execute(`MATCH (row:\`${label}\`) RETURN row LIMIT ${limit}`, dbName, isMultiDb).then(result => { return result.map(record => castInteger(record.row.properties)); }); }; -const getRelationshipData = (start, relationship, end, limit = 100, dbName, isMultiDb) => { +const getRelationshipData = ({ start, relationship, end, limit = 100, dbName, isMultiDb }) => { return execute( `MATCH (:\`${start}\`)-[row:\`${relationship}\`]-(:\`${end}\`) RETURN row LIMIT ${limit}`, dbName,