'use strict';
const OGMNeoObjectParse = require('./ogmneo-parse');
const OGMNeoRelationQuery = require('./ogmneo-relation-query');
const { OGMNeoOperation, OGMNeoOperationBuilder } = require('./ogmneo-operation');
const OGMNeoOperationExecuter = require('./ogmneo-operation-executer');
const _ = require('lodash');
/**
* @class OGMRelation
*/
class OGMRelation {
/**
* Creates a relation between two nodes if they both exists.
*
* @static
* @param {integer} nodeId - Id of the start node in the relation.
* @param {integer} otherNodeId - Id of the end node in the relation.
* @param {string} type - Case sensitive relation type name.
* @param {object} [properties={}] - Relation properties.
* @param {bool} [unique=false] - If include unique clause on create statement.
* @returns {Promise.<object|Error>} Created relation literal object if fulfilled, or some neo4j error if rejected.
*/
static relate(nodeId, type, otherNodeId, properties = {}, unique = false) {
try {
let operation = this.relateOperation(nodeId, type, otherNodeId, properties, unique);
return OGMNeoOperationExecuter.execute(operation);
} catch (error) {
return Promise.reject(error);
}
}
/**
* Operation that creates a relation between two nodes if they both exists.
*
* @static
* @param {integer} nodeId - First in relation node id.
* @param {integer} otherNodeId - Second in relation node id.
* @param {string} type - Case sensitive relation type name.
* @param {object} [properties={}] - Relation properties.
* @param {bool} [unique=false] - If include unique clause on create statement.
* @returns {OGMNeoOperation} Operation that creates the relation between nodes.
* @throws {Error} Will throw an error if the ids from node was not integers.
* @throws {Error} Will throw an error if a relatioship type was not an non-empty string.
*/
static relateOperation(nodeId, type, otherNodeId, properties = {}, unique = false) {
OGMNeoObjectParse.parseProperties(properties);
let value = _.omitBy(properties, _.isUndefined);
if (_.isInteger(nodeId) && _.isInteger(otherNodeId)) {
if (_.isString(type) && !_.isEmpty(type)) {
let uniqueQuery = (unique) ? 'UNIQUE' : '';
let cypher = `MATCH (n1) WHERE ID(n1)=${nodeId} ` +
`MATCH (n2) WHERE ID(n2)=${otherNodeId} ` +
`CREATE ${uniqueQuery} (n1)-[r:${type} ${OGMNeoObjectParse.objectString(value)}]->(n2) ` +
'RETURN r';
return OGMNeoOperationBuilder.create()
.cypher(cypher)
.type(OGMNeoOperation.WRITE)
.object(value)
.then((result) => {
let record = _.first(result.records);
return OGMNeoObjectParse.recordToRelation(record);
}).build();
} else {
throw new Error('A relatioship type must be specified');
}
} else {
throw new Error('Ids from node must to be integers');
}
}
/**
* Update a relation propeties if it exists.
*
* @static
* @param {integer} relationId - Relation node id.
* @param {object} newProperties - Relation NEW properties.
* @returns {Promise.<object|Error>} Updated relation literal object if fulfilled, or some neo4j error if rejected.
*/
static update(relationId, newProperties) {
try {
let operation = this.updateOperation(relationId, newProperties);
return OGMNeoOperationExecuter.execute(operation);
} catch (error) {
return Promise.reject(error);
}
}
/**
* Operation that updates a relation propeties if it exists.
*
* @static
* @param {integer} relationId - Relation node id.
* @param {object} newProperties - Relation NEW properties.
* @returns {OGMNeoOperation} Operation that creates the relation between nodes.
* @throws {Error} Will throw an error if the id from relation node was not integer.
*/
static updateOperation(relationId, newProperties) {
OGMNeoObjectParse.parseProperties(newProperties);
let value = _.omitBy(newProperties, _.isUndefined);
if (_.isInteger(relationId)) {
let propertiesString = OGMNeoObjectParse.objectString(value);
let cypher = 'MATCH p=(n1)-[r]->(n2) ' +
`WHERE ID(r)=${relationId} SET r+=${propertiesString} RETURN r`;
return OGMNeoOperationBuilder.create()
.cypher(cypher)
.type(OGMNeoOperation.WRITE)
.object(value)
.then((result) => {
let record = _.first(result.records);
return (record != null) ? OGMNeoObjectParse.recordToRelation(record) : null;
}).build();
} else {
throw new Error('Relation id must to be integer');
}
}
/**
* Set or update newPropeties on all relation nodes that matches parameters query.
*
* @static
* @param {object} newProperties - New properties ot be set or updated.
* @param {OGMNeoRelationQuery} query - Query filter.
* @returns {Promise.<array|Error>} Updated nodes if fulfilled, or some neo4j error if rejected.
*/
static updateMany(newProperties, query) {
try {
let operation = this.updateManyOperation(newProperties, query);
return OGMNeoOperationExecuter.execute(operation);
} catch (error) {
return Promise.reject(error);
}
}
/**
* Operation that set or update newPropeties on all relation nodes that matches parameters query.
*
* @static
* @param {object} newProperties - New properties ot be set or updated.
* @param {OGMNeoRelationQuery} query - Query filter.
* @returns {OGMNeoOperation} Operation that updates properties on the relation nodes.
* @throws {Error} Will throw an error if query was not an instance of ogmneo.RelationQuery.
* @throws {Error} Will throw an error if newProperties was not an object.
* @throws {Error} Will throw an error if newProperties was was empty.
*/
static updateManyOperation(newProperties, query) {
if (_.isObject(newProperties)) {
let value = _.omitBy(newProperties, _.isUndefined);
if (!_.isEmpty(value)) {
OGMNeoObjectParse.parseProperties(value);
if (query != null && query instanceof OGMNeoRelationQuery) {
let cypherMatch = query.matchCypher();
let propertiesString = OGMNeoObjectParse.objectString(value);
let cypher = `${cypherMatch} SET r+=${propertiesString} RETURN r`;
return OGMNeoOperationBuilder.create()
.cypher(cypher)
.type(OGMNeoOperation.WRITE)
.object(value)
.then((result) => {
return result.records.map(record => OGMNeoObjectParse.recordToRelation(record));
}).build();
} else {
throw new Error('The query object can\'t be null and must be an instance of OGMNeoRelationQuery');
}
} else {
throw new Error('newProperties must be an object with at least one valid property to update');
}
} else {
throw new Error('newProperties must be an object');
}
}
/**
* Find relation nodes.
*
* @static
* @param {OGMNeoRelationQuery} query - Query filter.
* @returns {Promise.<array|Error>} Found relation if fulfilled, or some neo4j error if rejected.
*/
static find(query) {
try {
let operation = this.findOperation(query);
return OGMNeoOperationExecuter.execute(operation);
} catch (error) {
return Promise.reject(error);
}
}
/**
* An operation that finds relation nodes that matches queries.
*
* @static
* @param {OGMNeoRelationQuery} query - Query filter.
* @returns {OGMNeoOperation} Operation that find the relation nodes.
* @throws {Error} Will throw an error if the query object was null or not an instance of OGMNeoRelationQuery.
*/
static findOperation(query) {
if (query != null && query instanceof OGMNeoRelationQuery) {
let cypher = query.queryCypher();
return OGMNeoOperationBuilder.create()
.cypher(cypher)
.type(OGMNeoOperation.READ)
.then((result) => {
return result.records.map(record => OGMNeoObjectParse.parseRelation(record));
}).build();
} else {
throw new Error('The query object can\'t be null and must be an instance of OGMNeoRelationQuery');
}
}
/**
* Find one relation node.
*
* @static
* @param {OGMNeoRelationQuery} query - Query filter.
* @returns {Promise.<object|Error>} Found relation if fulfilled, or 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 to find one relation node that matches query.
*
* @static
* @param {OGMNeoRelationQuery} query - Query filter.
* @returns {OGMNeoOperation} Operation that find one relation node.
* @throws {Error} Will throw an error if the query object was null or not an instance of OGMNeoRelationQuery.
*/
static findOneOperation(query) {
if (query != null && query instanceof OGMNeoRelationQuery) {
query.limit(1);
let operation = this.findOperation(query);
operation.then = (result) => {
let record = _.first(result.records);
return (record != null) ? OGMNeoObjectParse.parseRelation(record) : null;
};
return operation;
} else {
throw new Error('The query object can\'t be null and must be an instance of OGMNeoRelationQuery');
}
}
/**
* Find relation nodes with start and end nodes populated.
*
* @static
* @param {OGMNeoRelationQuery} query - Query filter.
* @returns {Promise.<array|Error>} Found populated relation if fulfilled, or some neo4j error if rejected.
*/
static findPopulated(query) {
try {
let operation = this.findPopulatedOperation(query);
return OGMNeoOperationExecuter.execute(operation);
} catch (error) {
return Promise.reject(error);
}
}
/**
* Operation that find relation nodes with start and end nodes populated.
*
* @static
* @param {OGMNeoRelationQuery} query - Query filter.
* @returns {OGMNeoOperation} Operation that find relations nodes.
* @throws {Error} Will throw an error if the query object was null or not an instance of ogmneo.RelationQuery.
*/
static findPopulatedOperation(query) {
if (query != null && query instanceof OGMNeoRelationQuery) {
let cypher = query.queryPopulatedCypher();
return OGMNeoOperationBuilder.create()
.cypher(cypher)
.type(OGMNeoOperation.READ)
.then((result) => {
return result.records.map(record => OGMNeoObjectParse.recordToRelationPopulated(record));
}).build();
} else {
throw new Error('The query object can\'t be null and must be an instance of OGMNeoRelationQuery');
}
}
/**
* Find one Populated relation node.
*
* @static
* @param {OGMNeoRelationQuery} query - Query filter.
* @returns {Promise.<object|Error>} Populated found relation if fulfilled, or some neo4j error if rejected.
*/
static findOnePopulated(query) {
try {
let operation = this.findOnePopulatedOperation(query);
return OGMNeoOperationExecuter.execute(operation);
} catch (error) {
return Promise.reject(error);
}
}
/**
* Operation to find one populated relation node.
*
* @static
* @param {OGMNeoRelationQuery} query - Query filter.
* @returns {OGMNeoOperation} Operation that find one populated relation node.
* @throws {Error} Will throw an error if the query object was null or not an instance of ogmneo.RelationQuery.
*/
static findOnePopulatedOperation(query) {
if (query != null && query instanceof OGMNeoRelationQuery) {
query.limit(1);
let operation = this.findPopulatedOperation(query);
operation.then = (result) => {
let record = _.first(result.records);
return (record != null) ? OGMNeoObjectParse.recordToRelationPopulated(record) : null;
};
return operation;
} else {
throw new Error('The query object can\'t be null and must be an instance of OGMNeoRelationQuery');
}
}
/**
* Find start and end nodes for the relation. Do not return relation properties to find relation properties use find or find populated.
*
* @static
* @param {OGMNeoRelationQuery} query - Query filter.
* @param {string} [nodes='both'] - The return nodes. 'both'/null = return start and end nodes, 'start' = return start nodes, 'end' = return end nodes
* @param {boolean} [distinct = false] - Add distinct clause to cypher return.
* @returns {Promise.<array|Error>} Found populated relation if fulfilled, or some neo4j error if rejected.
*/
static findNodes(query, nodes = 'both', distinct = false) {
try {
let operation = this.findNodesOperation(query, nodes, distinct);
return OGMNeoOperationExecuter.execute(operation);
} catch (error) {
return Promise.resolve(error);
}
}
/**
* Operation to find start and end nodes for the relation. Do not return relation properties to find relation properties use find or find populated.
*
* @static
* @param {OGMNeoRelationQuery} query - Query filter.
* @param {string} [nodes='both'] - The return nodes. 'both'/null = return start and end nodes, 'start' = return start nodes, 'end' = return end nodes
* @param {boolean} [distinct = false] - Add distinct clause to cypher return.
* @returns {OGMNeoOperation} Operation that find start and end nodes with query.
* @throws {Error} Will throw an error if the query object was null or not an instance of ogmneo.RelationQuery.
*/
static findNodesOperation(query, nodes = 'both', distinct = false) {
if (query != null && query instanceof OGMNeoRelationQuery) {
let cypher = query.queryNodesCypher(nodes, distinct);
return OGMNeoOperationBuilder.create()
.cypher(cypher)
.type(OGMNeoOperation.READ)
.then((result) => {
return result.records.map(record => OGMNeoObjectParse.recordToRelationStartEndNodes(record, nodes));
}).build();
} else {
throw new Error('The query object can\'t be null and must be an instance of OGMNeoRelationQuery');
}
}
/**
* Count relation nodes.
*
* @static
* @param {OGMNeoRelationQuery} query - Query filter.
* @returns {Promise.<integer|Error>} Count of relations if fulfilled, or some neo4j error if rejected.
*/
static count(query) {
try {
let operation = this.countOperation(query);
return OGMNeoOperationExecuter.execute(operation);
} catch (error) {
return Promise.reject(error);
}
}
/**
* Operation that counts relation nodes.
*
* @static
* @param {OGMNeoRelationQuery} query - Query filter.
* @returns {OGMNeoOperation} Operation that count nodes with query.
* @throws {Error} Will throw an error if the query object was null or not an instance of ogmneo.RelationQuery.
*/
static countOperation(query) {
if (query != null && query instanceof OGMNeoRelationQuery) {
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('The query object can\'t be null and must be an instance of OGMNeoRelationQuery');
}
}
/**
* Check if there is relation nodes that matches parameters query.
*
* @static
* @param {OGMNeoRelationQuery} query - Query filter.
* @returns {Promise.<boolean|Error>} True if there is some relation matching parameters and false otherwise if fulfilled, or some neo4j error if rejected.
*/
static exists(query) {
return new Promise((resolve, reject) => {
this.count(query)
.then((count) => {
resolve(count !== 0);
}).catch((error) => {
reject(error);
});
});
}
/**
* Delete relation by id.
*
* @static
* @param {integer} relationId - relation node id.
* @returns {Promise.<boolean|Error>} Deleted relation node if fulfilled, or some neo4j error if rejected.
*/
static deleteRelation(relationId) {
try {
let operation = this.deleteRelationOperation(relationId);
return OGMNeoOperationExecuter.execute(operation);
} catch (error) {
return Promise.reject(error);
}
}
/**
* Operation that deletes a relation by id.
*
* @static
* @param {integer} relationId - relation node id.
* @returns {OGMNeoOperation} Operation that deletes a node with id.
* @throws {Error} Will throw an error if the relation id was not an integer value.
*/
static deleteRelationOperation(relationId) {
if (_.isInteger(relationId)) {
let cypher = `MATCH p=(n1)-[r]->(n2) WHERE ID(r)=${relationId} DELETE r RETURN r`;
return OGMNeoOperationBuilder.create().cypher(cypher)
.type(OGMNeoOperation.WRITE)
.then((result) => {
let record = _.first(result.records);
return (record != null) ? OGMNeoObjectParse.recordToRelation(record) : null;
}).build();
} else {
throw new Error('Relation id must to be an integer number');
}
}
/**
* Deletes all relation nodes that matches parameters query.
*
* @static
* @param {OGMNeoRelationQuery} query - Query filter.
* @returns {Promise.<array|Error>} Deleted nodes 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);
}
}
/**
* Operation that deletes all relation nodes that matches parameters query.
*
* @static
* @param {OGMNeoRelationQuery} query - Query filter.
* @returns {OGMNeoOperation} Operation that deletes nodes with query.
* @throws {Error} Will throw an error if the query object was null or not an instance of ogmneo.RelationQuery.
*/
static deleteManyOperation(query) {
if (query != null && query instanceof OGMNeoRelationQuery) {
let cypherMatch = query.matchCypher();
let cypher = `${cypherMatch} DELETE r RETURN r`;
return OGMNeoOperationBuilder.create()
.cypher(cypher)
.type(OGMNeoOperation.WRITE)
.then((result) => {
return result.records.map(record => OGMNeoObjectParse.recordToRelation(record));
}).build();
} else {
throw new Error('The query object can\'t be null and must be an instance of OGMNeoRelationQuery');
}
}
}
module.exports = OGMRelation;