UNPKG

26.4 kBJavaScriptView Raw
1import provider from "../provider/provider";
2import dataModel from "../datamodel/dataModel";
3
4var query = {};
5
6/**
7 * Define the number of results displayed in result list.
8 */
9query.MAX_RESULTS_COUNT = 100;
10// query.RESULTS_PAGE_NUMBER = 1;
11query.VALUE_QUERY_LIMIT = 100;
12query.USE_PARENT_RELATION = false;
13query.USE_RELATION_DIRECTION = true;
14query.RETURN_LABELS = false;
15query.COLLECT_RELATIONS_WITH_VALUES = false;
16query.prefilter = "";
17query.prefilterParameters = {};
18
19query.applyPrefilters = function (queryStructure) {
20 queryStructure.statement = query.prefilter + queryStructure.statement;
21
22 Object.keys(query.prefilterParameters).forEach(function (key) {
23 queryStructure.parameters[key] = query.prefilterParameters[key];
24 });
25
26 return queryStructure;
27};
28
29/**
30 * Immutable constant object to identify Neo4j internal ID
31 */
32query.NEO4J_INTERNAL_ID = Object.freeze({queryInternalName: "NEO4JID"});
33
34/**
35 * Function used to filter returned relations
36 * return false if the result should be filtered out.
37 *
38 * @param d relation returned object
39 * @returns {boolean}
40 */
41query.filterRelation = function (d) {
42 return true;
43};
44
45/**
46 * Generate the query to count nodes of a label.
47 * If the label is defined as distinct in configuration the query will count only distinct values on constraint attribute.
48 */
49query.generateTaxonomyCountQuery = function (label) {
50 var constraintAttr = provider.node.getConstraintAttribute(label);
51
52 var whereElements = [];
53
54 var predefinedConstraints = provider.node.getPredefinedConstraints(label);
55 predefinedConstraints.forEach(function (predefinedConstraint) {
56 whereElements.push(predefinedConstraint.replace(new RegExp("\\$identifier", 'g'), "n"));
57 });
58
59 if (constraintAttr === query.NEO4J_INTERNAL_ID) {
60 return "MATCH (n:`" + label + "`)" + ((whereElements.length > 0) ? " WHERE " + whereElements.join(" AND ") : "") + " RETURN count(DISTINCT ID(n)) as count"
61 } else {
62 return "MATCH (n:`" + label + "`)" + ((whereElements.length > 0) ? " WHERE " + whereElements.join(" AND ") : "") + " RETURN count(DISTINCT n." + constraintAttr + ") as count"
63 }
64};
65
66query.generateNegativeQueryElements = function () {
67 var whereElements = [];
68 var parameters = {};
69
70 var negativeNodes = dataModel.nodes.filter(function (n) {
71 return n.isNegative === true;
72 });
73
74 negativeNodes.forEach(
75 function (n) {
76 if (provider.node.getGenerateNegativeNodeValueConstraints(n) !== undefined) {
77 var custom = provider.node.getGenerateNegativeNodeValueConstraints(n)(n);
78 whereElements = whereElements.concat(custom.whereElements);
79 for (var prop in custom.parameters) {
80 if (custom.parameters.hasOwnProperty(prop)) {
81 parameters[prop] = custom.parameters[prop];
82 }
83 }
84 } else {
85 var linksToRoot = query.getLinksToRoot(n, dataModel.links);
86
87 var i = linksToRoot.length - 1;
88 var statement = "(NOT exists(";
89
90 statement += "(" + dataModel.getRootNode().internalLabel + ")";
91
92 while (i >= 0) {
93 var l = linksToRoot[i];
94 var targetNode = l.target;
95
96 if (targetNode.isParentRelReverse === true && query.USE_RELATION_DIRECTION === true) {
97 statement += "<-";
98 } else {
99 statement += "-";
100 }
101
102 statement += "[:`" + l.label + "`]";
103
104 if (targetNode.isParentRelReverse !== true && query.USE_RELATION_DIRECTION === true) {
105 statement += "->";
106 } else {
107 statement += "-";
108 }
109
110 if (targetNode === n && targetNode.value !== undefined && targetNode.value.length > 0) {
111 var constraintAttr = provider.node.getConstraintAttribute(targetNode.label);
112 var paramName = targetNode.internalLabel + "_" + constraintAttr;
113
114 if (targetNode.value.length > 1) {
115 for (var pid = 0; pid < targetNode.value.length; pid++) {
116 parameters[paramName + "_" + pid] = targetNode.value[pid].attributes[constraintAttr];
117 }
118
119 statement += "(:`" + targetNode.label + "`{" + constraintAttr + ":$x$})";
120 } else {
121 parameters[paramName] = targetNode.value[0].attributes[constraintAttr];
122 statement += "(:`" + targetNode.label + "`{" + constraintAttr + ":$" + paramName + "})";
123 }
124 } else {
125 statement += "(:`" + targetNode.label + "`)";
126 }
127
128 i--;
129 }
130
131 statement += "))";
132
133 if (n.value !== undefined && n.value.length > 1) {
134 var cAttr = provider.node.getConstraintAttribute(n.label);
135 var pn = n.internalLabel + "_" + cAttr;
136
137 for (var nid = 0; nid < targetNode.value.length; nid++) {
138 whereElements.push(statement.replace("$x$", "$" + pn + "_" + nid));
139 }
140 } else {
141 whereElements.push(statement);
142 }
143 }
144 }
145 );
146
147 return {
148 "whereElements": whereElements,
149 "parameters": parameters
150 };
151};
152
153/**
154 * Generate Cypher query match and where elements from root node, selected node and a set of the graph links.
155 *
156 * @param rootNode root node in the graph.
157 * @param selectedNode graph target node.
158 * @param links list of links subset of the graph.
159 * @returns {{matchElements: Array, whereElements: Array}} list of match and where elements.
160 * @param isConstraintNeeded (used only for relation query)
161 * @param useCustomConstraints define whether to use the custom constraints (actually it is used only for results)
162 */
163query.generateQueryElements = function (rootNode, selectedNode, links, isConstraintNeeded, useCustomConstraints) {
164 var matchElements = [];
165 var whereElements = [];
166 var relationElements = [];
167 var returnElements = [];
168 var parameters = {};
169
170 var rootPredefinedConstraints = provider.node.getPredefinedConstraints(rootNode.label);
171
172 rootPredefinedConstraints.forEach(function (predefinedConstraint) {
173 whereElements.push(predefinedConstraint.replace(new RegExp("\\$identifier", 'g'), rootNode.internalLabel));
174 });
175
176 matchElements.push("(" + rootNode.internalLabel + ":`" + rootNode.label + "`)");
177
178 // Generate root node match element
179 if (isConstraintNeeded || rootNode.immutable) {
180 var rootValueConstraints = query.generateNodeValueConstraints(rootNode, useCustomConstraints);
181 whereElements = whereElements.concat(rootValueConstraints.whereElements);
182 for (var param in rootValueConstraints.parameters) {
183 if (rootValueConstraints.parameters.hasOwnProperty(param)) {
184 parameters[param] = rootValueConstraints.parameters[param];
185 }
186 }
187 }
188
189 var relId = 0;
190
191 // Generate match elements for each links
192 links.forEach(function (l) {
193 var sourceNode = l.source;
194 var targetNode = l.target;
195
196 var sourceRel = "";
197 var targetRel = "";
198
199 if (!query.USE_RELATION_DIRECTION) {
200 sourceRel = "-";
201 targetRel = "-";
202 } else {
203 if (targetNode.isParentRelReverse === true) {
204 sourceRel = "<-";
205 targetRel = "-";
206 } else {
207 sourceRel = "-";
208 targetRel = "->";
209 }
210 }
211
212 var relIdentifier = "r" + relId++;
213 relationElements.push(relIdentifier);
214 var predefinedConstraints = provider.node.getPredefinedConstraints(targetNode.label);
215
216 predefinedConstraints.forEach(function (predefinedConstraint) {
217 whereElements.push(predefinedConstraint.replace(new RegExp("\\$identifier", 'g'), targetNode.internalLabel));
218 });
219
220 if (query.COLLECT_RELATIONS_WITH_VALUES && targetNode === selectedNode) {
221 returnElements.push("COLLECT(" + relIdentifier + ") AS incomingRels");
222 }
223
224 var sourceLabelStatement = "";
225
226 if (!useCustomConstraints || provider.node.getGenerateNodeValueConstraints(sourceNode) === undefined) {
227 sourceLabelStatement = ":`" + sourceNode.label + "`";
228 }
229
230 var targetLabelStatement = "";
231
232 if (!useCustomConstraints || provider.node.getGenerateNodeValueConstraints(targetNode) === undefined) {
233 targetLabelStatement = ":`" + targetNode.label + "`";
234 }
235
236 matchElements.push("(" + sourceNode.internalLabel + sourceLabelStatement + ")" + sourceRel + "[" + relIdentifier + ":`" + l.label + "`]" + targetRel + "(" + targetNode.internalLabel + targetLabelStatement + ")");
237
238 if (targetNode !== selectedNode && (isConstraintNeeded || targetNode.immutable)) {
239 var nodeValueConstraints = query.generateNodeValueConstraints(targetNode, useCustomConstraints);
240 whereElements = whereElements.concat(nodeValueConstraints.whereElements);
241 for (var param in nodeValueConstraints.parameters) {
242 if (nodeValueConstraints.parameters.hasOwnProperty(param)) {
243 parameters[param] = nodeValueConstraints.parameters[param];
244 }
245 }
246 }
247 });
248
249 return {
250 "matchElements": matchElements,
251 "whereElements": whereElements,
252 "relationElements": relationElements,
253 "returnElements": returnElements,
254 "parameters": parameters
255 };
256};
257
258/**
259 * Generate the where and parameter statements for the nodes with value
260 *
261 * @param node the node to generate value constraints
262 * @param useCustomConstraints define whether to use custom generation in popoto config
263 */
264query.generateNodeValueConstraints = function (node, useCustomConstraints) {
265 if (useCustomConstraints && provider.node.getGenerateNodeValueConstraints(node) !== undefined) {
266 return provider.node.getGenerateNodeValueConstraints(node)(node);
267 } else {
268 var parameters = {}, whereElements = [];
269 if (node.value !== undefined && node.value.length > 0) {
270 var constraintAttr = provider.node.getConstraintAttribute(node.label);
271 var paramName;
272 if (constraintAttr === query.NEO4J_INTERNAL_ID) {
273 paramName = node.internalLabel + "_internalID";
274 } else {
275 paramName = node.internalLabel + "_" + constraintAttr;
276 }
277
278 if (node.value.length > 1) { // Generate IN constraint
279 parameters[paramName] = [];
280
281 node.value.forEach(function (value) {
282 var constraintValue;
283 if (constraintAttr === query.NEO4J_INTERNAL_ID) {
284 constraintValue = value.internalID
285 } else {
286 constraintValue = value.attributes[constraintAttr];
287 }
288
289 parameters[paramName].push(constraintValue);
290 });
291
292 if (constraintAttr === query.NEO4J_INTERNAL_ID) {
293 whereElements.push("ID(" + node.internalLabel + ") IN " + "$" + paramName);
294 } else {
295 whereElements.push(node.internalLabel + "." + constraintAttr + " IN " + "$" + paramName);
296 }
297 } else { // Generate = constraint
298 if (constraintAttr === query.NEO4J_INTERNAL_ID) {
299 parameters[paramName] = node.value[0].internalID;
300 } else {
301 parameters[paramName] = node.value[0].attributes[constraintAttr];
302 }
303
304 var operator = "=";
305
306 if (constraintAttr === query.NEO4J_INTERNAL_ID) {
307 whereElements.push("ID(" + node.internalLabel + ") " + operator + " " + "$" + paramName);
308 } else {
309 whereElements.push(node.internalLabel + "." + constraintAttr + " " + operator + " " + "$" + paramName);
310 }
311 }
312 }
313
314 return {
315 parameters: parameters,
316 whereElements: whereElements
317 }
318 }
319};
320
321/**
322 * Filter links to get only paths from root to leaf containing a value or being the selectedNode.
323 * All other paths in the graph containing no value are ignored.
324 *
325 * @param rootNode root node of the graph.
326 * @param targetNode node in the graph target of the query.
327 * @param initialLinks list of links representing the graph to filter.
328 * @returns {Array} list of relevant links.
329 */
330query.getRelevantLinks = function (rootNode, targetNode, initialLinks) {
331 var links = initialLinks.slice();
332 var finalLinks = [];
333
334 // Filter all links to keep only those containing a value or being the selected node.
335 // Negatives nodes are handled separately.
336 var filteredLinks = links.filter(function (l) {
337 return l.target === targetNode || ((l.target.value !== undefined && l.target.value.length > 0) && (!l.target.isNegative === true));
338 });
339
340 // All the filtered links are removed from initial links list.
341 filteredLinks.forEach(function (l) {
342 links.splice(links.indexOf(l), 1);
343 });
344
345 // Then all the intermediate links up to the root node are added to get only the relevant links.
346 filteredLinks.forEach(function (fl) {
347 var sourceNode = fl.source;
348 var search = true;
349
350 while (search) {
351 var intermediateLink = null;
352 links.forEach(function (l) {
353 if (l.target === sourceNode) {
354 intermediateLink = l;
355 }
356 });
357
358 if (intermediateLink === null) { // no intermediate links needed
359 search = false
360 } else {
361 if (intermediateLink.source === rootNode) {
362 finalLinks.push(intermediateLink);
363 links.splice(links.indexOf(intermediateLink), 1);
364 search = false;
365 } else {
366 finalLinks.push(intermediateLink);
367 links.splice(links.indexOf(intermediateLink), 1);
368 sourceNode = intermediateLink.source;
369 }
370 }
371 }
372 });
373
374 return filteredLinks.concat(finalLinks);
375};
376
377/**
378 * Get the list of link defining the complete path from node to root.
379 * All other links are ignored.
380 *
381 * @param node The node where to start in the graph.
382 * @param links
383 */
384query.getLinksToRoot = function (node, links) {
385 var pathLinks = [];
386 var targetNode = node;
387
388 while (targetNode !== dataModel.getRootNode()) {
389 var nodeLink;
390
391 for (var i = 0; i < links.length; i++) {
392 var link = links[i];
393 if (link.target === targetNode) {
394 nodeLink = link;
395 break;
396 }
397 }
398
399 if (nodeLink) {
400 pathLinks.push(nodeLink);
401 targetNode = nodeLink.source;
402 }
403 }
404
405 return pathLinks;
406};
407
408/**
409 * Generate a Cypher query to retrieve the results matching the current graph.
410 *
411 * @param isGraph
412 * @returns {{statement: string, parameters: (*|{})}}
413 */
414query.generateResultQuery = function (isGraph) {
415 var rootNode = dataModel.getRootNode();
416 var negativeElements = query.generateNegativeQueryElements();
417 var queryElements = query.generateQueryElements(rootNode, rootNode, query.getRelevantLinks(rootNode, rootNode, dataModel.links), true, true);
418 var queryMatchElements = queryElements.matchElements,
419 queryWhereElements = queryElements.whereElements.concat(negativeElements.whereElements),
420 queryRelationElements = queryElements.relationElements,
421 queryReturnElements = [],
422 queryEndElements = [],
423 queryParameters = queryElements.parameters;
424
425 for (var prop in negativeElements.parameters) {
426 if (negativeElements.parameters.hasOwnProperty(prop)) {
427 queryParameters[prop] = negativeElements.parameters[prop];
428 }
429 }
430
431 // Sort results by specified attribute
432 var resultOrderByAttribute = provider.node.getResultOrderByAttribute(rootNode.label);
433
434 if (resultOrderByAttribute !== undefined && resultOrderByAttribute !== null) {
435 var sorts = [];
436 var order = provider.node.isResultOrderAscending(rootNode.label);
437
438 var orders = [];
439 if (Array.isArray(order)) {
440 orders = order.map(function (v) {
441 return v ? "ASC" : "DESC";
442 });
443 } else {
444 orders.push(order ? "ASC" : "DESC");
445 }
446
447 if (Array.isArray(resultOrderByAttribute)) {
448 sorts = resultOrderByAttribute.map(function (ra) {
449 var index = resultOrderByAttribute.indexOf(ra);
450
451 if (index < orders.length) {
452 return ra + " " + orders[index];
453 } else {
454 return ra + " " + orders[orders.length - 1];
455 }
456 })
457
458 } else {
459 sorts.push(resultOrderByAttribute + " " + orders[0]);
460 }
461
462 queryEndElements.push("ORDER BY " + sorts.join(", "));
463 }
464
465 queryEndElements.push("LIMIT " + query.MAX_RESULTS_COUNT);
466
467 if (isGraph) {
468 // Only return relations
469 queryReturnElements.push(rootNode.internalLabel);
470 queryRelationElements.forEach(
471 function (el) {
472 queryReturnElements.push(el);
473 }
474 );
475 } else {
476 var resultAttributes = provider.node.getReturnAttributes(rootNode.label);
477
478 queryReturnElements = resultAttributes.map(function (attribute) {
479 if (attribute === query.NEO4J_INTERNAL_ID) {
480 return "ID(" + rootNode.internalLabel + ") AS " + query.NEO4J_INTERNAL_ID.queryInternalName;
481 } else {
482 return rootNode.internalLabel + "." + attribute + " AS " + attribute;
483 }
484 });
485
486 if (query.RETURN_LABELS === true) {
487 var element = "labels(" + rootNode.internalLabel + ")";
488
489 if (resultAttributes.indexOf("labels") < 0) {
490 element = element + " AS labels";
491 }
492
493 queryReturnElements.push(element);
494 }
495 }
496
497 var queryStatement = "MATCH " + queryMatchElements.join(", ") + ((queryWhereElements.length > 0) ? " WHERE " + queryWhereElements.join(" AND ") : "") + " RETURN DISTINCT " + queryReturnElements.join(", ") + " " + queryEndElements.join(" ");
498
499 // Filter the query if defined in config
500 var queryStructure = provider.node.filterResultQuery(rootNode.label, {
501 statement: queryStatement,
502 matchElements: queryMatchElements,
503 whereElements: queryWhereElements,
504 withElements: [],
505 returnElements: queryReturnElements,
506 endElements: queryEndElements,
507 parameters: queryParameters
508 });
509
510 return query.applyPrefilters(queryStructure);
511};
512
513/**
514 * Generate a cypher query to the get the node count, set as parameter matching the current graph.
515 *
516 * @param countedNode the counted node
517 * @returns {string} the node count cypher query
518 */
519query.generateNodeCountQuery = function (countedNode) {
520 var negativeElements = query.generateNegativeQueryElements();
521 var queryElements = query.generateQueryElements(dataModel.getRootNode(), countedNode, query.getRelevantLinks(dataModel.getRootNode(), countedNode, dataModel.links), true, true);
522 var queryMatchElements = queryElements.matchElements,
523 queryWhereElements = queryElements.whereElements.concat(negativeElements.whereElements),
524 queryReturnElements = [],
525 queryEndElements = [],
526 queryParameters = queryElements.parameters;
527
528 for (var prop in negativeElements.parameters) {
529 if (negativeElements.parameters.hasOwnProperty(prop)) {
530 queryParameters[prop] = negativeElements.parameters[prop];
531 }
532 }
533
534 var countAttr = provider.node.getConstraintAttribute(countedNode.label);
535
536 if (countAttr === query.NEO4J_INTERNAL_ID) {
537 queryReturnElements.push("count(DISTINCT ID(" + countedNode.internalLabel + ")) as count");
538 } else {
539 queryReturnElements.push("count(DISTINCT " + countedNode.internalLabel + "." + countAttr + ") as count");
540 }
541
542 var queryStatement = "MATCH " + queryMatchElements.join(", ") + ((queryWhereElements.length > 0) ? " WHERE " + queryWhereElements.join(" AND ") : "") + " RETURN " + queryReturnElements.join(", ");
543
544 // Filter the query if defined in config
545 var queryStructure = provider.node.filterNodeCountQuery(countedNode, {
546 statement: queryStatement,
547 matchElements: queryMatchElements,
548 whereElements: queryWhereElements,
549 returnElements: queryReturnElements,
550 endElements: queryEndElements,
551 parameters: queryParameters
552 });
553
554 return query.applyPrefilters(queryStructure);
555};
556
557/**
558 * Generate a Cypher query from the graph model to get all the possible values for the targetNode element.
559 *
560 * @param targetNode node in the graph to get the values.
561 * @returns {string} the query to execute to get all the values of targetNode corresponding to the graph.
562 */
563query.generateNodeValueQuery = function (targetNode) {
564 var negativeElements = query.generateNegativeQueryElements();
565 var rootNode = dataModel.getRootNode();
566 var queryElements = query.generateQueryElements(rootNode, targetNode, query.getRelevantLinks(rootNode, targetNode, dataModel.links), true, false);
567 var queryMatchElements = queryElements.matchElements,
568 queryWhereElements = queryElements.whereElements.concat(negativeElements.whereElements),
569 queryReturnElements = [],
570 queryEndElements = [],
571 queryParameters = queryElements.parameters;
572
573 for (var prop in negativeElements.parameters) {
574 if (negativeElements.parameters.hasOwnProperty(prop)) {
575 queryParameters[prop] = negativeElements.parameters[prop];
576 }
577 }
578
579 // Sort results by specified attribute
580 var valueOrderByAttribute = provider.node.getValueOrderByAttribute(targetNode.label);
581 if (valueOrderByAttribute) {
582 var order = provider.node.isValueOrderAscending(targetNode.label) ? "ASC" : "DESC";
583 queryEndElements.push("ORDER BY " + valueOrderByAttribute + " " + order);
584 }
585
586 queryEndElements.push("LIMIT " + query.VALUE_QUERY_LIMIT);
587
588 var resultAttributes = provider.node.getReturnAttributes(targetNode.label);
589 var constraintAttribute = provider.node.getConstraintAttribute(targetNode.label);
590
591 for (var i = 0; i < resultAttributes.length; i++) {
592 if (resultAttributes[i] === query.NEO4J_INTERNAL_ID) {
593 queryReturnElements.push("ID(" + targetNode.internalLabel + ") AS " + query.NEO4J_INTERNAL_ID.queryInternalName);
594 } else {
595 queryReturnElements.push(targetNode.internalLabel + "." + resultAttributes[i] + " AS " + resultAttributes[i]);
596 }
597 }
598
599 // Add count return attribute on root node
600 var rootConstraintAttr = provider.node.getConstraintAttribute(rootNode.label);
601
602 if (rootConstraintAttr === query.NEO4J_INTERNAL_ID) {
603 queryReturnElements.push("count(DISTINCT ID(" + rootNode.internalLabel + ")) AS count");
604 } else {
605 queryReturnElements.push("count(DISTINCT " + rootNode.internalLabel + "." + rootConstraintAttr + ") AS count");
606 }
607
608 if (query.COLLECT_RELATIONS_WITH_VALUES) {
609 queryElements.returnElements.forEach(function (re) {
610 queryReturnElements.push(re);
611 });
612 }
613
614 var queryStatement = "MATCH " + queryMatchElements.join(", ") + ((queryWhereElements.length > 0) ? " WHERE " + queryWhereElements.join(" AND ") : "") + " RETURN " + queryReturnElements.join(", ") + " " + queryEndElements.join(" ");
615
616 // Filter the query if defined in config
617 var queryStructure = provider.node.filterNodeValueQuery(targetNode, {
618 statement: queryStatement,
619 matchElements: queryMatchElements,
620 whereElements: queryWhereElements,
621 returnElements: queryReturnElements,
622 endElements: queryEndElements,
623 parameters: queryParameters
624 });
625
626 return query.applyPrefilters(queryStructure);
627};
628
629/**
630 * Generate a Cypher query to retrieve all the relation available for a given node.
631 *
632 * @param targetNode
633 * @returns {string}
634 */
635query.generateNodeRelationQuery = function (targetNode) {
636
637 var linksToRoot = query.getLinksToRoot(targetNode, dataModel.links);
638
639 var queryElements = query.generateQueryElements(dataModel.getRootNode(), targetNode, linksToRoot, false, false);
640 var queryMatchElements = queryElements.matchElements,
641 queryWhereElements = queryElements.whereElements,
642 queryReturnElements = [],
643 queryEndElements = [],
644 queryParameters = queryElements.parameters;
645
646 var rel = query.USE_RELATION_DIRECTION ? "->" : "-";
647
648 queryMatchElements.push("(" + targetNode.internalLabel + ":`" + targetNode.label + "`)-[r]" + rel + "(x)");
649 queryReturnElements.push("type(r) AS label");
650 if (query.USE_PARENT_RELATION) {
651 queryReturnElements.push("head(labels(x)) AS target");
652 } else {
653 queryReturnElements.push("last(labels(x)) AS target");
654 }
655 queryReturnElements.push("count(r) AS count");
656 queryEndElements.push("ORDER BY count(r) DESC");
657
658 var queryStatement = "MATCH " + queryMatchElements.join(", ") + ((queryWhereElements.length > 0) ? " WHERE " + queryWhereElements.join(" AND ") : "") + " RETURN " + queryReturnElements.join(", ") + " " + queryEndElements.join(" ");
659 // Filter the query if defined in config
660 var queryStructure = provider.node.filterNodeRelationQuery(targetNode, {
661 statement: queryStatement,
662 matchElements: queryMatchElements,
663 whereElements: queryWhereElements,
664 returnElements: queryReturnElements,
665 endElements: queryEndElements,
666 parameters: queryParameters
667 });
668
669 return query.applyPrefilters(queryStructure);
670};
671
672export default query
\No newline at end of file