Comparing version 0.4.0 to 0.5.0
@@ -14,3 +14,3 @@ /** | ||
Object.defineProperty(loader, 'version', { | ||
value: '0.4.0' | ||
value: '0.5.0' | ||
}); | ||
@@ -17,0 +17,0 @@ |
{ | ||
"name": "decypher", | ||
"version": "0.4.0", | ||
"version": "0.5.0", | ||
"description": "A handful of cypher utilities for Node.js", | ||
@@ -31,2 +31,3 @@ "main": "index.js", | ||
"dependencies": { | ||
"escape-regexp": "0.0.1", | ||
"lodash.assign": "^3.2.0", | ||
@@ -33,0 +34,0 @@ "lodash.isplainobject": "^3.2.0" |
@@ -12,2 +12,7 @@ [![Build Status](https://travis-ci.org/Yomguithereal/decypher.svg)](https://travis-ci.org/Yomguithereal/decypher) | ||
* Miscellaneous [helpers](#helpers). | ||
* [escapeIdentifier](#identifier) | ||
* [escapeLiteralMap](#literal-map) | ||
* [nodePattern](#node-pattern) | ||
* [relationshipPattern](#relationship-pattern) | ||
* [searchPattern](#search-pattern) | ||
@@ -176,3 +181,3 @@ ## Installation | ||
*Escaping identifiers* | ||
<em id="identifier">Escaping identifiers</em> | ||
@@ -186,3 +191,3 @@ ```js | ||
*Escaping literal maps* | ||
<em id="literal-map">Escaping literal maps</em> | ||
@@ -206,3 +211,3 @@ ```js | ||
*Building node patterns* | ||
<em id="node-pattern">Building node patterns</em> | ||
@@ -223,2 +228,5 @@ ```js | ||
helpers.nodePattern('n'); | ||
>>> '(n)' | ||
helpers.nodePattern({ | ||
@@ -251,3 +259,3 @@ identifier: 'n', | ||
*Building relationship patterns* | ||
<em id="relationship-pattern">Building relationship patterns</em> | ||
@@ -265,2 +273,4 @@ ```js | ||
// * `paramKeys`: will be passed to escapeLiteralMap when stringifying data | ||
// * `source`: the source node (passed to the `nodePattern` function) | ||
// * `target`: the target node (passed to the `nodePattern` function) | ||
@@ -270,2 +280,5 @@ helpers.relationshipPattern(); | ||
helpers.relationshipPattern('r'); | ||
>>> '-[r]-' | ||
helpers.relationshipPattern({ | ||
@@ -296,4 +309,36 @@ direction: 'out', | ||
>>> '-[:KNOWS {since: 1975}]-' | ||
helpers.relationshipPattern({ | ||
direction: 'out', | ||
predicate: 'PLAYED_IN', | ||
source: 'a', | ||
target: { | ||
identifier: 'm', | ||
label: 'Movie' | ||
} | ||
}); | ||
>>> '(a)-[:PLAYED_IN]->(m:Movie)' | ||
``` | ||
<em id="search-pattern">Building search patterns</em> | ||
Note that it will escape for query for regular expression use through the [`escape-regexp`](https://www.npmjs.com/package/escape-regexp) module. | ||
```js | ||
var helpers = require('decypher').helpers; | ||
// Possible options are: | ||
// * `flags` [`'ius'`]: Flags for the regular expression. | ||
// * `partial` [`true`]: Should the match be partial (wrapped in `.*query.*`)? | ||
helpers.searchPattern('john'); | ||
>>> '(?ius).*john.*' | ||
helpers.searchPattern('john', {flags: 'im'}); | ||
>>> '(?im).*john.*' | ||
helpers.searchPattern('john', {flags: null, partial: false}); | ||
>>> 'john' | ||
``` | ||
## Contribution | ||
@@ -300,0 +345,0 @@ |
253
src/batch.js
@@ -13,13 +13,8 @@ /** | ||
var escapeIdentifier = helpers.escapeIdentifier, | ||
nodePattern = helpers.nodePattern, | ||
relationshipPattern = helpers.relationshipPattern; | ||
// TODO: RETURN rather object for nodes? | ||
/** | ||
* Helpers. | ||
*/ | ||
function isInternal(id) { | ||
return /^i/.test('' + id); | ||
} | ||
function buildObject(key, value) { | ||
@@ -34,5 +29,42 @@ if (!key) | ||
function validData(o) { | ||
return o && !!Object.keys(o).length; | ||
} | ||
/** | ||
* Class. | ||
* Handler classes. | ||
*/ | ||
function Node(batch, id, data, labels) { | ||
var external = id || id === 0; | ||
// Properties | ||
this.batch = batch; | ||
this.id = id || null; | ||
this.identifier = 'N' + (!external ? 'i' + (batch.nodesCounter++) : 'e' + id); | ||
this.param = 'p' + this.identifier; | ||
this.data = data || null; | ||
this.labels = labels; | ||
this.external = external; | ||
} | ||
function Edge(batch, id, source, predicate, target, data) { | ||
var external = id || id === 0; | ||
// Properties | ||
this.batch = batch; | ||
this.id = id || null; | ||
this.identifier = 'R' + (!external ? 'i' + (batch.edgesCounter++) : 'e' + id); | ||
this.param = 'p' + this.identifier; | ||
this.source = source; | ||
this.target = target; | ||
this.predicate = predicate; | ||
this.data = data || null; | ||
this.external = external; | ||
} | ||
/** | ||
* Main class. | ||
*/ | ||
function Batch() { | ||
@@ -42,2 +74,3 @@ | ||
this.nodesCounter = 0; | ||
this.edgesCounter = 0; | ||
this.nodes = {}; | ||
@@ -52,12 +85,10 @@ this.externalNodes = {}; | ||
*/ | ||
Batch.prototype._externalNode = function(id, data) { | ||
var identifier = 'e' + id; | ||
Batch.prototype._externalNode = function(id) { | ||
if (typeof id !== 'string' && typeof id !== 'number') | ||
throw Error('decypher.Batch: invalid node. Should be either a node handler or a raw Neo4j id.'); | ||
if (!this.externalNodes[identifier]) | ||
this.externalNodes[identifier] = {id: identifier}; | ||
if (!this.externalNodes[id]) | ||
this.externalNodes[id] = new Node(this, id, {}); | ||
if (data) | ||
this.externalNodes[identifier].data = data; | ||
return this.externalNodes[identifier]; | ||
return this.externalNodes[id]; | ||
}; | ||
@@ -80,27 +111,19 @@ | ||
// Giving an id to the created node | ||
var id = 'i' + (this.nodesCounter++); | ||
var node = new Node(this, null, data, labels); | ||
this.nodes[node.identifier] = node; | ||
this.nodes[id] = {data: data, labels: labels}; | ||
return id; | ||
return node; | ||
} | ||
// Updating a node | ||
Batch.prototype.update = function(id, data) { | ||
Batch.prototype.update = function(node, data) { | ||
if (!isPlainObject(data)) | ||
throw Error('decypher.Batch.update: provided data is not an object.'); | ||
if (isInternal(id)) { | ||
var node = this.nodes[id]; | ||
if (!(node instanceof Node)) | ||
node = this._externalNode(node); | ||
if (!node) | ||
throw Error('decypher.Batch.update: the given internal node id is not defined.'); | ||
node.data = assign({}, node.data, data); | ||
node.data = assign({}, node.data, data); | ||
} | ||
else { | ||
this._externalNode(id, data); | ||
} | ||
return this; | ||
return node; | ||
}; | ||
@@ -119,60 +142,30 @@ | ||
// Relate two nodes | ||
Batch.prototype.relate = function(a, predicate, b, data) { | ||
Batch.prototype.relate = function(source, predicate, target, data) { | ||
data = data || null; | ||
if (!isInternal(a)) { | ||
var node = this._externalNode(a); | ||
a = node.id; | ||
} | ||
else { | ||
var node = this.nodes[a]; | ||
if (!(source instanceof Node)) | ||
source = this._externalNode(source); | ||
if (!node) | ||
throw Error('decypher.Batch.relate: the given internal node id for the source is not defined.'); | ||
} | ||
if (!(target instanceof Node)) | ||
target = this._externalNode(target); | ||
if (!isInternal(b)) { | ||
var node = this._externalNode(b); | ||
b = node.id; | ||
} | ||
else { | ||
var node = this.nodes[b]; | ||
var edge = new Edge(this, null, source, predicate, target, data); | ||
if (!node) | ||
throw Error('decypher.Batch.relate: the given internal node id for the target is not defined.'); | ||
} | ||
this.edges.push(edge); | ||
this.edges.push({from: a, to: b, predicate: predicate, data: data}); | ||
// TODO: Return the edge id? | ||
return this; | ||
return edge; | ||
}; | ||
// Unrelate two nodes | ||
Batch.prototype.unrelate = function(a, predicate, b) { | ||
// TODO: how to delete a relation strictly | ||
Batch.prototype.unrelate = function(source, predicate, target) { | ||
if (!isInternal(a)) { | ||
var node = this._externalNode(a); | ||
a = node.id; | ||
} | ||
else { | ||
var node = this.nodes[a]; | ||
if (!(source instanceof Node)) | ||
source = this._externalNode(source); | ||
if (!node) | ||
throw Error('decypher.Batch.unrelate: the given internal node id for the source is not defined.'); | ||
} | ||
if (!(target instanceof Node)) | ||
target = this._externalNode(target); | ||
if (!isInternal(b)) { | ||
var node = this._externalNode(b); | ||
b = node.id; | ||
} | ||
else { | ||
var node = this.nodes[b]; | ||
if (!node) | ||
throw Error('decypher.Batch.unrelate: the given internal node id for the target is not defined.'); | ||
} | ||
this.unlinks.push({from: a, to: b, predicate: predicate}); | ||
return this; | ||
this.unlinks.push({source: source, target: target, predicate: predicate}); | ||
return; | ||
}; | ||
@@ -185,17 +178,17 @@ | ||
Batch.prototype.query = function() { | ||
var query = new Query(); | ||
var query = new Query(), | ||
matchSegment = query.segment(), | ||
createSegment = query.segment(); | ||
var matches = [], | ||
lines = []; | ||
//-- External nodes | ||
//-- Registering external nodes | ||
Object.keys(this.externalNodes).forEach(function(k) { | ||
var node = this.externalNodes[k], | ||
name = 'n' + k, | ||
propName = 'p' + k; | ||
var node = this.externalNodes[k]; | ||
matches.push({name: name, id: node.id}); | ||
matchSegment | ||
.match(nodePattern({identifier: node.identifier})) | ||
.where('id(' + node.identifier + ') = ' + node.id); | ||
if (node.data) | ||
lines.push({type: 'update', name: name, propName: propName, data: node.data}); | ||
if (validData(node.data)) | ||
query.set(node.identifier + ' += {' + node.param + '}', buildObject(node.param, node.data)); | ||
}, this); | ||
@@ -205,11 +198,12 @@ | ||
Object.keys(this.nodes).forEach(function(k) { | ||
var node = this.nodes[k], | ||
name = 'n' + k, | ||
propName = 'p' + k; | ||
var node = this.nodes[k]; | ||
lines.push({type: 'create', name: name, propName: propName, data: node.data}); | ||
createSegment | ||
.create(nodePattern({identifier: node.identifier, data: node.param}), buildObject(node.param, node.data)); | ||
node.labels.forEach(function(label) { | ||
lines.push({type: 'label', name: name, label: label}); | ||
var labels = node.labels.map(function(label) { | ||
return node.identifier + ':' + escapeIdentifier(label); | ||
}); | ||
query.set(labels); | ||
}, this); | ||
@@ -219,64 +213,29 @@ | ||
this.unlinks.forEach(function(unlink, i) { | ||
matches.push({ | ||
from: unlink.from, | ||
predicate: unlink.predicate, | ||
to: unlink.to, | ||
index: i | ||
}); | ||
matchSegment | ||
.match(relationshipPattern({ | ||
direction: 'out', | ||
identifier: 'U' + i, | ||
predicate: unlink.predicate, | ||
source: unlink.source.identifier, | ||
target: unlink.target.identifier | ||
})); | ||
lines.push({type: 'unrelate', index: i}); | ||
query.delete('U' + i); | ||
}); | ||
//-- Edges | ||
this.edges.forEach(function(edge) { | ||
lines.push({ | ||
type: 'relate', | ||
from: edge.from, | ||
to: edge.to, | ||
predicate: edge.predicate | ||
}); | ||
}); | ||
//-- Building the query | ||
matches.forEach(function(match) { | ||
if (match.from) { | ||
var pattern = relationshipPattern({ | ||
this.edges.forEach(function(edge, i) { | ||
createSegment | ||
.create(relationshipPattern({ | ||
direction: 'out', | ||
identifier: 'r' + match.index, | ||
predicate: match.predicate | ||
}); | ||
predicate: edge.predicate, | ||
data: edge.data ? edge.param : null, | ||
source: edge.source.identifier, | ||
target: edge.target.identifier | ||
})); | ||
query.match('(n' + match.from + ')' + pattern + '(n' + match.to + ')'); | ||
} | ||
else { | ||
query.match('(' + match.name + ')'); | ||
query.where('id(' + match.name + ') = ' + match.id); | ||
} | ||
if (validData(edge.data)) | ||
query.params(buildObject(edge.param, edge.data)); | ||
}); | ||
lines.forEach(function(line) { | ||
var p = buildObject(line.propName, line.data); | ||
if (line.type === 'update') { | ||
query.set(line.name + ' += {' + line.propName + '}', p); | ||
} | ||
else if (line.type === 'create') { | ||
query.create('(' + line.name + ' {' + line.propName + '})', p); | ||
} | ||
else if (line.type === 'label') { | ||
query.set(line.name + ':`' + escapeIdentifier(line.label) + '`'); | ||
} | ||
else if (line.type === 'relate') { | ||
var pattern = relationshipPattern({ | ||
direction: 'out', | ||
predicate: line.predicate | ||
}); | ||
query.create('(n' + line.from + ')' + pattern + '(n' + line.to + ')'); | ||
} | ||
else if (line.type === 'unrelate') { | ||
query.delete('r' + line.index); | ||
} | ||
}); | ||
return query; | ||
@@ -283,0 +242,0 @@ }; |
@@ -7,5 +7,8 @@ /** | ||
*/ | ||
var isPlainObject = require('lodash.isplainobject'); | ||
var isPlainObject = require('lodash.isplainobject'), | ||
escapeRegexp = require('escape-regexp'), | ||
syntax = require('./syntax.js'); | ||
var KEYWORDS = require('./syntax.js').KEYWORDS; | ||
var KEYWORDS = syntax.KEYWORDS, | ||
REGEX_FLAGS = syntax.REGEX_FLAGS; | ||
@@ -48,2 +51,5 @@ // Escaping an identifier | ||
if (typeof opts === 'string') | ||
return nodePattern({identifier: opts}); | ||
if (!isPlainObject(opts)) | ||
@@ -77,2 +83,5 @@ throw Error('decypher.helpers.nodePattern: given options should be a plain object.'); | ||
if (typeof opts === 'string') | ||
return relationshipPattern({identifier: opts}); | ||
if (!isPlainObject(opts)) | ||
@@ -83,2 +92,5 @@ throw Error('decypher.helpers.relationshipPattern: given options should be a plain object.'); | ||
if (opts.source) | ||
pattern += nodePattern(opts.source); | ||
if (opts.direction === 'in') | ||
@@ -118,5 +130,41 @@ pattern += '<-'; | ||
if (opts.target) | ||
pattern += nodePattern(opts.target); | ||
return pattern; | ||
} | ||
// Creating a search pattern | ||
function searchPattern(query, opts) { | ||
opts = opts || {}; | ||
if (typeof query !== 'string') | ||
throw Error('decypher.helpers.searchPattern: given query should be a string.'); | ||
var flags = 'flags' in opts ? opts.flags : 'ius', | ||
partial = opts.partial !== false; | ||
if (typeof flags === 'string') { | ||
if (flags.split('').some(function(flag) { | ||
return !REGEX_FLAGS[flag]; | ||
})) | ||
throw Error('decypher.helpers.searchPattern: invalid flags "' + flags + '".'); | ||
} | ||
var pattern = ''; | ||
if (flags) | ||
pattern += '(?' + flags + ')'; | ||
if (partial) | ||
pattern += '.*'; | ||
pattern += escapeRegexp(query); | ||
if (partial) | ||
pattern += '.*'; | ||
return pattern; | ||
} | ||
module.exports = { | ||
@@ -126,3 +174,4 @@ escapeIdentifier: escapeIdentifier, | ||
nodePattern: nodePattern, | ||
relationshipPattern: relationshipPattern | ||
relationshipPattern: relationshipPattern, | ||
searchPattern: searchPattern | ||
}; |
@@ -8,2 +8,17 @@ /** | ||
*/ | ||
var flags = [ | ||
'i', | ||
'x', | ||
'm', | ||
's', | ||
'u', | ||
'd' | ||
]; | ||
var REGEX_FLAGS = {}; | ||
flags.forEach(function(flag) { | ||
REGEX_FLAGS[flag] = true; | ||
}); | ||
var STATEMENTS = [ | ||
@@ -50,2 +65,3 @@ 'ASSERT', | ||
'ASC', | ||
'CONTAINS', | ||
'DESC', | ||
@@ -68,3 +84,4 @@ 'ENDS', | ||
exports.REGEX_FLAGS = REGEX_FLAGS; | ||
exports.STATEMENTS = STATEMENTS; | ||
exports.KEYWORDS = KEYWORDS; |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
25897
587
358
3
+ Addedescape-regexp@0.0.1
+ Addedescape-regexp@0.0.1(transitive)