diff --git a/src/utils/cypher/queryParser.ts b/src/utils/cypher/queryParser.ts index cbb0a96b2ebb457adf0d2ecfd62cd1a1b7cd88a6..3fc22501b740643a7490981d277b54701d5a0d12 100644 --- a/src/utils/cypher/queryParser.ts +++ b/src/utils/cypher/queryParser.ts @@ -1,146 +1,147 @@ -import { Record, Node, Relationship, Path, PathSegment, isRelationship, isNode, isPath, isPathSegment, Integer } from 'neo4j-driver'; -import { log } from '../../logger'; -import type { EdgeQueryResult, NodeQueryResult } from 'ts-common'; -import type { GraphQueryResultFromBackend } from 'ts-common'; +import { Record, Node, Relationship, Path, PathSegment, isRelationship, isNode, isPath, isPathSegment, Integer } from "neo4j-driver"; +import { log } from "../../logger"; +import type { EdgeQueryResult, NodeQueryResult } from "ts-common"; +import type { GraphQueryResultFromBackend } from "ts-common"; export function parseCypherQuery(result: Record[], returnType: "nodelink" | "table" = "nodelink"): GraphQueryResultFromBackend { + try { try { - try { - switch (returnType) { - case "nodelink": - return parseNodeLinkQuery(result); - case "table": - // TODO: add table support later - // return parseGroupByQuery(result.records as any); - log.error(`Table format not supported yet`); - throw new Error("Table format not supported yet"); - default: - log.error(`Error Unknown query Format`); - throw new Error("Unknown query Format"); - } - } catch (err) { - log.error(`Parsing failed ${err}`); - throw err; - } - + switch (returnType) { + case "nodelink": + return parseNodeLinkQuery(result); + case "table": + // TODO: add table support later + // return parseGroupByQuery(result.records as any); + log.error(`Table format not supported yet`); + throw new Error("Table format not supported yet"); + default: + log.error(`Error Unknown query Format`); + throw new Error("Unknown query Format"); + } } catch (err) { - log.error(`Error executing query`, err); - throw err; + log.error(`Parsing failed ${err}`); + throw err; } + } catch (err) { + log.error(`Error executing query`, err); + throw err; + } } function parseNodeLinkQuery(results: Record[]): GraphQueryResultFromBackend { - let nodes: NodeQueryResult[] = []; - let edges: EdgeQueryResult[] = []; - let nodeIds: Set<string> = new Set(); - let edgeIds: Set<string> = new Set(); - const result: GraphQueryResultFromBackend = { nodes, edges }; - - for (let i = 0; i < results.length; i++) { - const resList = results[i]; - for (let j = 0; j < resList.length; j++) { - const res = resList.get(j); - parseNodeLinkEntity(res, nodes, edges, nodeIds, edgeIds); - } + let nodes: NodeQueryResult[] = []; + let edges: EdgeQueryResult[] = []; + let nodeIds: Set<string> = new Set(); + let edgeIds: Set<string> = new Set(); + const result: GraphQueryResultFromBackend = { nodes, edges }; + + for (let i = 0; i < results.length; i++) { + const resList = results[i]; + for (let j = 0; j < resList.length; j++) { + const res = resList.get(j); + parseNodeLinkEntity(res, nodes, edges, nodeIds, edgeIds); } + } - return result; + return result; } -function parseNodeLinkEntity(res: Node | Relationship | Path | PathSegment, nodes: NodeQueryResult[], edges: EdgeQueryResult[], nodeIds: Set<string>, edgeIds: Set<string>): void { - if (isRelationship(res)) { - const neoEdge = parseEdge(res); - if (!edgeIds.has(neoEdge._id)) { - edgeIds.add(neoEdge._id); - edges.push(neoEdge); - } - } else if (isNode(res)) { - const neoNode = parseNode(res); - if (!nodeIds.has(neoNode._id)) { - nodeIds.add(neoNode._id); - nodes.push(neoNode); - } - } else if (isPath(res)) { - parseNodeLinkEntity(res.start, nodes, edges, nodeIds, edgeIds); - for (const segment of res.segments) { - parseNodeLinkEntity(segment.relationship, nodes, edges, nodeIds, edgeIds); - parseNodeLinkEntity(segment.end, nodes, edges, nodeIds, edgeIds); - } - parseNodeLinkEntity(res.end, nodes, edges, nodeIds, edgeIds); - } else if (isPathSegment(res)) { - parseNodeLinkEntity(res.start, nodes, edges, nodeIds, edgeIds); - parseNodeLinkEntity(res.relationship, nodes, edges, nodeIds, edgeIds); - parseNodeLinkEntity(res.end, nodes, edges, nodeIds, edgeIds); - } else { - log.warn(`Ignoring Unknown type ${res}`); +function parseNodeLinkEntity( + res: Node | Relationship | Path | PathSegment, + nodes: NodeQueryResult[], + edges: EdgeQueryResult[], + nodeIds: Set<string>, + edgeIds: Set<string> +): void { + if (isRelationship(res)) { + const neoEdge = parseEdge(res); + if (!edgeIds.has(neoEdge._id)) { + edgeIds.add(neoEdge._id); + edges.push(neoEdge); + } + } else if (isNode(res)) { + const neoNode = parseNode(res); + if (!nodeIds.has(neoNode._id)) { + nodeIds.add(neoNode._id); + nodes.push(neoNode); + } + } else if (isPath(res)) { + parseNodeLinkEntity(res.start, nodes, edges, nodeIds, edgeIds); + for (const segment of res.segments) { + parseNodeLinkEntity(segment.relationship, nodes, edges, nodeIds, edgeIds); + parseNodeLinkEntity(segment.end, nodes, edges, nodeIds, edgeIds); } + parseNodeLinkEntity(res.end, nodes, edges, nodeIds, edgeIds); + } else if (isPathSegment(res)) { + parseNodeLinkEntity(res.start, nodes, edges, nodeIds, edgeIds); + parseNodeLinkEntity(res.relationship, nodes, edges, nodeIds, edgeIds); + parseNodeLinkEntity(res.end, nodes, edges, nodeIds, edgeIds); + } else { + log.warn(`Ignoring Unknown type ${res}`); + } } function parseNode(node: Node): NodeQueryResult { - return { - _id: node.identity.toString(), - label: node.labels[0], // TODO: only using first label - attributes: Object.fromEntries(Object.entries(node.properties)?.map(([k, v]) => { - if (Integer.isInteger(v)) { - return [k, v.toNumber()]; - } + return { + _id: node.identity.toString(), + label: node.labels[0], // TODO: only using first label + attributes: Object.fromEntries( + Object.entries(node.properties)?.map(([k, v]) => { + if (Integer.isInteger(v)) { + return [k, v.toNumber()]; + } - if (v instanceof Object) { - // TODO: for now, we don't support nested objects - return [k, JSON.stringify(v)]; - } - return [k, v]; - })), - }; + return [k, v]; + }) + ), + }; } - function parseEdge(edge: Relationship): EdgeQueryResult { - return { - _id: edge.identity.toString(), - label: edge.type, - from: edge.start.toString(), - to: edge.end.toString(), - attributes: { ...edge.properties, type: edge.type }, - }; + return { + _id: edge.identity.toString(), + label: edge.type, + from: edge.start.toString(), + to: edge.end.toString(), + attributes: { ...edge.properties, type: edge.type }, + }; } - function parseGroupByQuery(results: Record<any, string>[]): any { - // TODO: not tested, since we don't use this yet - - let groupKey: string; - let byKey: string; - const group: string[] = []; - const by: string[] = []; - const result: { [key: string]: any } = {}; - - if (results[0].keys.length !== 2) { - throw new Error("invalid result format"); - } - - // Checks if the first letter of the first key is either an e or an r, since that would make it the By-key - // By-keys start with e0_attr or r0_attr and group keys with an aggregation funcion such as AVG_attr or MIN_attr - // This will work when an aggregation function starts with an E or R, since e != E - if (results[0].keys[0][0] === 'e' || results[0].keys[0][0] === 'r') { - byKey = results[0].keys[0]; - groupKey = results[0].keys[1]; - } else { - byKey = results[0].keys[1]; - groupKey = results[0].keys[0]; + // TODO: not tested, since we don't use this yet + + let groupKey: string; + let byKey: string; + const group: string[] = []; + const by: string[] = []; + const result: { [key: string]: any } = {}; + + if (results[0].keys.length !== 2) { + throw new Error("invalid result format"); + } + + // Checks if the first letter of the first key is either an e or an r, since that would make it the By-key + // By-keys start with e0_attr or r0_attr and group keys with an aggregation funcion such as AVG_attr or MIN_attr + // This will work when an aggregation function starts with an E or R, since e != E + if (results[0].keys[0][0] === "e" || results[0].keys[0][0] === "r") { + byKey = results[0].keys[0]; + groupKey = results[0].keys[1]; + } else { + byKey = results[0].keys[1]; + groupKey = results[0].keys[0]; + } + + // Form proper result structure + for (const res of results) { + const g = res.get(groupKey); + const b = res.get(byKey); + + if (g !== null && b !== null) { + group.push(g.toString()); + by.push(b.toString()); } + } - // Form proper result structure - for (const res of results) { - const g = res.get(groupKey); - const b = res.get(byKey); + result["group"] = { name: groupKey, values: group }; + result["by"] = { name: byKey, values: by }; - if (g !== null && b !== null) { - group.push(g.toString()); - by.push(b.toString()); - } - } - - result["group"] = { name: groupKey, values: group }; - result["by"] = { name: byKey, values: by }; - - return result; -} \ No newline at end of file + return result; +}