'use strict';
const _ = require('lodash');
const OGMNeoQuery = require('./ogmneo-query');
const OGMNeoObjectParse = require('./ogmneo-parse');
const { OGMNeoOperation, OGMNeoOperationBuilder } = require('./ogmneo-operation');
const OGMNeoOperationExecuter = require('./ogmneo-operation-executer');
/**
* @class OGMNeoNode
*/
class OGMNeoNode {
/**
* Creates a node on neo4j.
*
* @static
* @param {object} node - The literal object with node propeperties.
* @param {string} [label=null] - The label of the node. Default null is a node without label.
* @returns {Promise<object|Error>} Created node literal object if fulfilled, or some neo4j error if rejected.
*/
static create(node, label = null) {
let operation = this.createOperation(node, label);
return OGMNeoOperationExecuter.execute(operation);
}
/**
* Operation that creates a node on neo4j.
*
* @static
* @param {object} node - The literal object with node propeperties.
* @param {string} [label=null] - The label of the node. Default null is a node without label.
* @returns {OGMNeoOperation} Create node operation that can be executed later.
*/
static createOperation(node, label = null) {
let value = _.omitBy(node, _.isUndefined);
OGMNeoObjectParse.parseProperties(value);
let objectString = OGMNeoObjectParse.objectString(value);
let labelCypher = (!_.isEmpty(label) && _.isString(label)) ? `:${label}` : '';
let cypher = `CREATE (n${labelCypher} ${objectString}) RETURN n`;
return OGMNeoOperationBuilder.create()
.cypher(cypher)
.object(value)
.type(OGMNeoOperation.WRITE).then((result) => {
let record = _.first(result.records);
return OGMNeoObjectParse.parseRecordNode(record, 'n');
}).build();
}
/**
* Updates a node on neo4j.
*
* @static
* @param {object} node - The literal object with node propeperties and required node.id.
* @returns {Promise.<object|Error>} Updated node literal object if fulfilled, or error if node.id is invalid or some neo4j error if rejected.
*/
static update(node) {
try {
let operation = this.updateOperation(node);
return OGMNeoOperationExecuter.execute(operation);
} catch (error) {
return Promise.reject(error);
}
}
/**
* Operation that updates a node.
*
* @static
* @param {object} node - The literal object with node propeperties and required node.id.
* @returns {OGMNeoOperation} Update node operation that can be executed later.
* @throws {Error} Will throw an error if the node.id was not integer or not exists.
*/
static updateOperation(node) {
let value = _.omitBy(node, _.isUndefined);
OGMNeoObjectParse.parseProperties(value);
if (value && value.id != undefined && _.isInteger(value.id)) {
let objectString = OGMNeoObjectParse.objectString(value);
let cypher = `MATCH (n) WHERE ID(n)=${node.id} SET n+=${objectString} RETURN n`;
return OGMNeoOperationBuilder.create()
.cypher(cypher)
.object(value)
.type(OGMNeoOperation.WRITE).then((result) => {
let record = _.first(result.records);
return OGMNeoObjectParse.parseRecordNode(record, 'n');
}).build();
} else {
throw new Error('Node must have an integer id to be updated');
}
}
/**
* Update new properties on every node that matches the query.
*
* @static
* @param {OGMNeoQuery} query - The query to filter the nodes.
* @param {object} newProperties - NEW properties.
* @returns {Promise.<array|Error>} Updated nodes if fulfilled or some neo4j error if rejected.
*/
static updateMany(query, newProperties) {
try {
let operation = this.updateManyOperation(query, newProperties);
return OGMNeoOperationExecuter.execute(operation);
} catch (error) {
return Promise.reject(error);
}
}
/**
* Returns an operation that updates new properties on every node that matches the query.
*
* @static
* @param {OGMNeoQuery} query - The query to filter the nodes.
* @param {object} newProperties - NEW properties.
* @returns {OGMNeoOperation} Operation that updates new properties on every node that matches the query.
* @throws {Error} Will throw an error if the query is not an instance of ogmneo.Query.
* @throws {Error} Will throw an error if newProperties is not an object.
* @throws {Error} Will throw an error if newProperties don't have at least one property with NO undefined values to update.
*/
static updateManyOperation(query, newProperties) {
if (_.isObject(newProperties)) {
let value = _.omitBy(newProperties, _.isUndefined);
if (!_.isEmpty(value)) {
OGMNeoObjectParse.parseProperties(value);
if (query instanceof OGMNeoQuery) {
let objectString = OGMNeoObjectParse.objectString(value);
let cypher = `${query.matchCypher()} SET n+=${objectString} RETURN n`;
return OGMNeoOperationBuilder.create()
.cypher(cypher)
.object(value)
.type(OGMNeoOperation.WRITE).then((result) => {
return result.records.map(record => OGMNeoObjectParse.parseRecordNode(record, 'n'));
}).build();
} else {
throw new Error('The query object must be an instance of OGMNeoQuery');
}
} else {
throw new Error('You must provide at least one property with NO undefined values to update');
}
} else {
throw new Error('The new properties must be an object');
}
}
/**
* Deletes a node on neo4j.
*
* @static
* @param {object} node - The literal object with node propeperties and required node.id.
* @returns {Promise.<boolean|Error>} True if fulfilled and found and delete node, false if not found object to delete, or error if node.id is invalid or some neo4j error if rejected.
*/
static delete(node) {
try {
let operation = this.deleteOperation(node);
return OGMNeoOperationExecuter.execute(operation);
} catch (error) {
return Promise.reject(error);
}
}
/**
* Operation that deletes a node on neo4j.
*
* @static
* @param {object} node - The literal object with node propeperties and required node.id.
* @returns {OGMNeoOperation} Operation that deletes a node.
* @throws {Error} Will throw an error if the node.id was not integer or not exists.
*/
static deleteOperation(node) {
if (node && node.id != undefined && _.isInteger(node.id)) {
let cypher = `MATCH (n) WHERE ID(n)=${node.id} DELETE n RETURN n`;
return OGMNeoOperationBuilder.create()
.cypher(cypher)
.type(OGMNeoOperation.WRITE).then((result) => {
return !_.isEmpty(result.records);
}).build();
} else {
throw new Error('Node must to have an non-nil property id to be deleted');
}
}
/**
* Deletes a node and it's relation from database.
*
* @static
* @param {object} node - The literal object with node propeperties and required node.id.
* @returns {Promise.<boolean|Error>} True if fulfilled and found and delete node, false if not found object to delete, or error if node.id is invalid or some neo4j error if rejected.
*/
static deleteCascade(node) {
try {
let operation = this.deleteCascadeOperation(node);
return OGMNeoOperationExecuter.execute(operation);
} catch (error) {
return Promise.reject(error);
}
}
/**
* Operation that deletes a node and it's relation from database.
*
* @static
* @param {object} node - The literal object with node propeperties and required node.id.
* @returns {OGMNeoOperation} Operation that deletes a node.
* @throws {Error} Will throw an error if the node.id was not integer or not exists.
*/
static deleteCascadeOperation(node) {
if (node && node.id != undefined && _.isInteger(node.id)) {
let cypher = `MATCH (n) WHERE ID(n)=${node.id} DETACH DELETE n RETURN n`;
return OGMNeoOperationBuilder.create()
.cypher(cypher)
.type(OGMNeoOperation.WRITE).then((result) => {
return !_.isEmpty(result.records);
}).build();
} else {
throw new Error('Node must to have an non-nil property id to be deleted');
}
}
/**
* Deletes every node that matches the ogmneo.Query.
*
* @static
* @param {OGMNeoQuery} query - The query to filter the nodes.
* @returns {Promise.<number|Error>} Number of nodes deleted if fulfilled or some neo4j error if rejected.
*/
static deleteMany(query) {
try {
let operation = this.deleteManyOperation(query);
return OGMNeoOperationExecuter.execute(operation);
} catch (error) {
return Promise.reject(error);
}
}
/**
* Creates an operation that deletes every node that matches the query.
*
* @static
* @param {OGMNeoQuery} query - The query to filter the nodes.
* @returns {OGMNeoOperation} Operation that deletes the matched nodes.
* @throws {Error} Will throw an error if the query was not a instance of ogmneo.Query.
*/
static deleteManyOperation(query) {
if (query instanceof OGMNeoQuery) {
let cypher = `${query.matchCypher()} DELETE n RETURN n`;
return OGMNeoOperationBuilder.create()
.cypher(cypher)
.type(OGMNeoOperation.WRITE)
.then((result) => {
return result.records.length;
}).build();
} else {
throw new Error('The query object must be an instance of OGMNeoQuery');
}
}
/**
* Retrive node with id.
*
* @static
* @param {integer} id - The id of node that's wanted.
* @returns {Promise.<object|Error>} Object if found fulfilled or null if not found fulfilled, or error if the id is invalid or some neo4j error if rejected.
*/
static nodeWithId(id) {
try {
let operation = this.nodeWithIdOperation(id);
return OGMNeoOperationExecuter.execute(operation);
} catch (error) {
return Promise.reject(error);
}
}
/**
* Creates a operation that retrives a node with id.
*
* @static
* @param {integer} id - The id of node that's wanted.
* @returns {OGMNeoOperation} Operation retrives a node.
* @throws {Error} Will throw an error if id was not an integer value.
*/
static nodeWithIdOperation(id) {
if (_.isInteger(id)) {
let cypher = `MATCH (n) WHERE ID(n)=${id} RETURN n`;
return OGMNeoOperationBuilder.create()
.cypher(cypher)
.type(OGMNeoOperation.READ)
.then((result) => {
let record = _.first(result.records);
return OGMNeoObjectParse.parseRecordNode(record, 'n');
}).build();
} else {
throw new Error('You must provide an non-null integer id property to find the node');
}
}
/**
* Retrive nodes with ids.
*
* @static
* @param {array} ids - The ids of nodes that are wanted.
* @returns {Promise.<array|Error>} An array of nodes if found fulfilled or null if not found fulfilled, or error if the ids are invalid or some neo4j error if rejected.
*/
static manyWithIds(ids) {
try {
let operation = this.manyWithIdsOperation(ids);
return OGMNeoOperationExecuter.execute(operation);
} catch (error) {
return Promise.reject(error);
}
}
/**
* Operation to retrive nodes with ids.
*
* @static
* @param {array} ids - The ids of nodes that are wanted.
* @returns {OGMNeoOperation} Operation that query the nodes with the ids.
* @throws {Error} Will throw an error if you don't provide any valid id to retrive.
* @throws {Error} Will throw an error if the ids are not an array.
*/
static manyWithIdsOperation(ids) {
if (_.isArray(ids)) {
let validIds = ids.filter(id => _.isInteger(id));
if (_.isEmpty(validIds)) {
throw new Error('You must provide at least one valid(integer) id to query');
} else {
let idsQuery = validIds.reduce((result, current) => {
return result + ((result === '') ? '' : ',') + ` ${current}`;
}, '');
let cypher = `MATCH (n) WHERE ID(n) IN [${idsQuery}] RETURN n`;
return OGMNeoOperationBuilder.create()
.cypher(cypher)
.type(OGMNeoOperation.READ)
.then((result) => {
return result.records.map(record => OGMNeoObjectParse.parseRecordNode(record, 'n'));
}).build();
}
} else {
throw new Error('The parameter must be an array of ids');
}
}
/**
* Count of nodes with the label.
*
* @static
* @param {string} label - The label of nodes that have to be counted.
* @returns {Promise.<integer|Error>} Count of nodes if fulfilled, some neo4j error if rejected.
*/
static countWithLabel(label) {
return this.count(new OGMNeoQuery(label));
}
/**
* Count of nodes with the query.
*
* @static
* @param {OGMNeoQuery} query - The query to filter nodes that have to be counted.
* @returns {Promise.<integer|Error>} Count of nodes if fulfilled, some neo4j error if rejected.
*/
static count(query) {
try {
let operation = this.countOperation(query);
return OGMNeoOperationExecuter.execute(operation);
} catch (error) {
return Promise.reject(error);
}
}
/**
* Creates an operation count of nodes with a query object.
*
* @static
* @param {OGMNeoQuery} query - The query to filter nodes that have to be counted.
* @returns {OGMNeoOperation} Operation that query the count of the nodes with query.
* @throws {Error} Will throw an error if the query was not a instance of ogmneo.Query.
*/
static countOperation(query) {
if (query && query instanceof OGMNeoQuery) {
let cypher = query.countCypher();
return OGMNeoOperationBuilder.create()
.cypher(cypher)
.type(OGMNeoOperation.READ)
.then((result) => {
let record = _.first(result.records);
return (record != null) ? record.get('count').low : 0;
}).build();
} else {
throw new Error('A OGMNeoQuery object must to be provided');
}
}
/**
* Find nodes filtered by query parameter.
*
* @static
* @param {OGMNeoQuery} query - The query to filter nodes that have to be returned.
* @returns {Promise.<array|Error>} Nodes if fulfilled, some neo4j error if rejected.
*/
static find(query) {
try {
let operation = this.findOperation(query);
return OGMNeoOperationExecuter.execute(operation);
} catch (error) {
return Promise.reject(error);
}
}
/**
* Operation for find nodes filtered by the query parameter.
*
* @static
* @param {OGMNeoQuery} query - The query to filter nodes that have to be returned.
* @returns {OGMNeoOperation} Operation that returns the nodes with query.
* @throws {Error} Will throw an error if the query was not a instance of ogmneo.Query.
*/
static findOperation(query) {
if (query && query instanceof OGMNeoQuery) {
let cypher = query.queryCypher();
return OGMNeoOperationBuilder.create()
.cypher(cypher)
.type(OGMNeoOperation.READ)
.then((result) => {
return result.records.map((record) => OGMNeoObjectParse.parseRecordNode(record, 'n'));
}).build();
} else {
throw new Error('A OGMNeoQuery object must to be provided');
}
}
/**
* Find one node filtered by query parameter. Will return the first node that it finds.
*
* @static
* @param {OGMNeoQuery} query - The query to filter nodes that have to be returned.
* @returns {Promise.<object|Error>} Node found if fulfilled, some neo4j error if rejected.
*/
static findOne(query) {
try {
let operation = this.findOneOperation(query);
return OGMNeoOperationExecuter.execute(operation);
} catch (error) {
return Promise.reject(error);
}
}
/**
* Operation that find one node filtered by query parameter.
*
* @static
* @param {OGMNeoQuery} query The query to filter nodes that have to be returned.
* @returns {OGMNeoOperation} Operation that returns the node with query.
* @throws {Error} Will throw an error if the query was not a instance of ogmneo.Query.
*/
static findOneOperation(query) {
if (query && query instanceof OGMNeoQuery) {
query.limit(1);
return OGMNeoOperationBuilder.create()
.cypher(query.queryCypher())
.type(OGMNeoOperation.READ)
.then((result) => {
let record = _.first(result.records);
return (record != null) ? OGMNeoObjectParse.parseRecordNode(record, 'n') : null;
}).build();
} else {
throw new Error('A OGMNeoQuery object must to be provided');
}
}
/**
* Adding label to a node.
*
* @static
* @param {string} label - The label to be added to the node.
* @param {integer} nodeId - The id of the node to add the label.
* @returns {Promise.<object|Error>} Node(if node exists) or null(if not exists) if fulfilled, some error if rejected.
*/
static addLabelToNode(label, nodeId) {
try {
let operation = this.addLabelToNodeOperation(label, nodeId);
return OGMNeoOperationExecuter.execute(operation);
} catch (error) {
return Promise.reject(error);
}
}
/**
* Operation to add a label to a node.
*
* @static
* @param {string} label - The label to be added to the node.
* @param {integer} nodeId - The id of the node to add the label.
* @returns {OGMNeoOperation} Operation that adds a label.
* @throws {Error} Will throw an error if the nodeId was not an integer value.
* @throws {Error} Will throw an error if the label was anything diferent than an non-empty string.
*/
static addLabelToNodeOperation(label, nodeId) {
if (_.isInteger(nodeId)) {
let operation = this.addLabelToNodesOperation(label, [nodeId]);
operation.then = (result) => {
let record = _.first(result.records);
return (record != null) ? OGMNeoObjectParse.parseRecordNode(record, 'n') : null;
};
return operation;
} else {
throw new Error('The nodeId must be an integer value');
}
}
/**
* Remove label from a node.
*
* @static
* @param {string} label - The label to be removed from the node.
* @param {integer} nodeId - The id of the node to remove the label from.
* @returns {Promise.<object|Error>} Node(if node exists) or null(if not exists) if fulfilled, some error if rejected.
*/
static removeLabelFromNode(label, nodeId) {
try {
let operation = this.removeLabelFromNodeOperation(label, nodeId);
return OGMNeoOperationExecuter.execute(operation);
} catch (error) {
return Promise.reject(error);
}
}
/**
* Operation to remove a label from a node.
*
* @static
* @param {string} label - The label to be removed from the node.
* @param {integer} nodeId - The id of the node to remove the label from.
* @returns {OGMNeoOperation} Operation that removes a label.
* @throws {Error} Will throw an error if the nodeId was not an integer value.
* @throws {Error} Will throw an error if the label was anything diferent than an non-empty string.
*/
static removeLabelFromNodeOperation(label, nodeId) {
if (_.isInteger(nodeId)) {
let operation = this.removeLabelFromNodesOperation(label, [nodeId]);
operation.then = (result) => {
let record = _.first(result.records);
return (record != null) ? OGMNeoObjectParse.parseRecordNode(record, 'n') : null;
};
return operation;
} else {
throw new Error('The nodeId must be an integer value');
}
}
/**
* Adding label to nodes.
*
* @static
* @param {string} label - The label to be added to the node.
* @param {array} nodesIds - The ids of the nodes to add the label.
* @returns {Promise<array|Error>} Nodes(if nodes exists) or null(if not exists) if fulfilled, some error if rejected.
*/
static addLabelToNodes(label, nodesIds) {
try {
let operation = this.addLabelToNodesOperation(label, nodesIds);
return OGMNeoOperationExecuter.execute(operation);
} catch (error) {
return Promise.reject(error);
}
}
/**
* Operation that adds a label to nodes.
*
* @static
* @param {string} label - The label to be added to the node.
* @param {array} nodesIds - The ids of the nodes to add the label.
* @returns {OGMNeoOperation} Operation that removes a label.
* @throws {Error} Will throw an error if you don't provide at least one valid id to this operation.
* @throws {Error} Will throw an error if the label was anything diferent than an non-empty string.
*/
static addLabelToNodesOperation(label, nodesIds) {
if (_.isString(label) && !_.isEmpty(label)) {
let params = this._validateAndBuildParams(nodesIds);
if (params != null) {
let cypher = `MATCH (n) WHERE ID(n) IN ${params} SET n:${label} RETURN n`;
return OGMNeoOperationBuilder
.create()
.cypher(cypher)
.type(OGMNeoOperation.WRITE)
.then((result) => {
return result.records.map(record => OGMNeoObjectParse.parseRecordNode(record, 'n'));
}).build();
} else {
throw new Error('You must provide at least one valid id to this operation');
}
} else {
throw new Error('label must be a non empty string');
}
}
/**
* Remove label from nodes.
*
* @static
* @param {string} label - The label to be removed from the nodes.
* @param {array} nodeIds - The ids of the nodes to remove the label from.
* @returns {Promise.<array|Error>} Nodes(if nodes exists) or null(if not exists) if fulfilled, some error if rejected.
*/
static removeLabelFromNodes(label, nodesIds) {
try {
let operation = this.removeLabelFromNodesOperation(label, nodesIds);
return OGMNeoOperationExecuter.execute(operation);
} catch (error) {
return Promise.reject(error);
}
}
/**
* Operation that removes a label from nodes.
*
* @static
* @param {string} label - The label to be removed from the nodes.
* @param {array} nodeIds - The ids of the nodes to remove the label from.
* @returns {OGMNeoOperation} Operation that removes a label from many nodes.
* @throws {Error} Will throw an error if you don't provide at least one valid id to this operation.
* @throws {Error} Will throw an error if the label was anything diferent than an non-empty string.
*/
static removeLabelFromNodesOperation(label, nodesIds) {
if (_.isString(label) && !_.isEmpty(label)) {
let params = this._validateAndBuildParams(nodesIds);
if (params) {
let cypher = `MATCH (n:${label}) WHERE ID(n) IN ${params} REMOVE n:${label} RETURN n`;
return OGMNeoOperationBuilder
.create()
.cypher(cypher)
.type(OGMNeoOperation.WRITE)
.then((result) => {
return result.records.map(record => OGMNeoObjectParse.parseRecordNode(record, 'n'));
}).build();
} else {
throw new Error('You must provide at least one valid id to this operation');
}
} else {
throw new Error('label must be a non empty string');
}
}
static _validateAndBuildParams(nodesIds) {
if (_.isArray(nodesIds)) {
let validIds = nodesIds.filter(id => _.isInteger(id));
if (_.isEmpty(validIds)) {
return null;
} else {
let parameters = validIds.reduce((result, current) => {
return result + `${(result === '') ? '' : ','} ${current}`;
}, '');
return `[${parameters}]`;
}
} else {
throw new Error('nodesIds must be an array');
}
}
}
module.exports = OGMNeoNode;