diff --git a/src/readers/queryService.ts b/src/readers/queryService.ts index 448459b9f3219f8a7b3d46506c151a761473b147..d1b8f3ae91d43fcf46edb76fd6c10eb2ae77519e 100644 --- a/src/readers/queryService.ts +++ b/src/readers/queryService.ts @@ -79,7 +79,15 @@ export const queryServiceReader = async (frontendPublisher: RabbitMqBroker, mlPu log.debug('Received query request:', message, headers, visualQuery); if (visualQuery.nodes.length === 0) { log.info('Empty query received'); - publisher.publishResultToFrontend({ nodes: [], edges: [] }); + publisher.publishResultToFrontend({ + nodes: [], + edges: [], + metaData: { + topological: { density: 0, self_loops: 0 }, + nodes: { count: 0, labels: [], types: {} }, + edges: { count: 0, labels: [], types: {} }, + }, + }); return; } diff --git a/src/utils/cypher/converter/export.ts b/src/utils/cypher/converter/export.ts index fa3921339d041f30820f5aff5c8a24a0f26388e0..1682a30438e548a6b96ca0d2d5353f3a6cce5cf3 100644 --- a/src/utils/cypher/converter/export.ts +++ b/src/utils/cypher/converter/export.ts @@ -19,5 +19,5 @@ export function extractExportCypher(JSONQuery: NodeStruct): string[] { const relationCypher = extractExportCypher(JSONQuery.relation.node); exports.push(...relationCypher); } - return exports; + return [...new Set(exports)]; } diff --git a/src/utils/cypher/converter/logic.ts b/src/utils/cypher/converter/logic.ts index 2e81c85b0a629fcb5db630bb4e296516c7caf412..40369839f8d25c8b252a6726245027b86c0b7235 100644 --- a/src/utils/cypher/converter/logic.ts +++ b/src/utils/cypher/converter/logic.ts @@ -2,40 +2,38 @@ import type { AnyStatement } from 'ts-common/src/model/query/logic/general'; import type { QueryCacheData } from './model'; import { log } from 'ts-common/src/logger/logger'; -export function createWhereLogic(op: string, left: string, whereLogic: string, cacheData: QueryCacheData): [string, string] { +export function createWhereLogic( + op: string, + left: string, + whereLogic: Set<string>, + cacheData: QueryCacheData, +): { logic: string; where: Set<string> } { const newWhereLogic = `${left.replace('.', '_')}_${op}`; - if (whereLogic) { - whereLogic += ', '; - } - const remainingNodes: string[] = []; - - for (const entity of cacheData.entities) { - if (entity.id !== left) { - remainingNodes.push(entity.id); - } - } // TODO: Relation temporarily ignored due to unnecessary added complexity in the query // for (const relation of cacheData.relations) { // if (relation.id !== left) { - // remainingNodes.push(relation.id); + // remainingNodes.add(relation.id); // } // } - const remainingNodesStr = remainingNodes.length > 0 ? `, ${remainingNodes.join(', ')}` : ''; - const newWithLogic = `${whereLogic}${op}(${left}) AS ${newWhereLogic}${remainingNodesStr}`; + whereLogic.add(`${op}(${left}) AS ${newWhereLogic}`); + for (const entity of cacheData.entities) { + if (entity.id !== left) { + whereLogic.add(entity.id); + } + } - return [newWhereLogic, newWithLogic]; + return { logic: newWhereLogic, where: whereLogic }; } -export function extractLogicCypher(logicQuery: AnyStatement, cacheData: QueryCacheData): [string, string] { +export function extractLogicCypher(logicQuery: AnyStatement, cacheData: QueryCacheData): { logic: string; where: Set<string> } { switch (typeof logicQuery) { case 'object': if (Array.isArray(logicQuery)) { let op = logicQuery[0].replace('_', '').toLowerCase(); const right = logicQuery?.[2]; - const [left, whereLogic] = extractLogicCypher(logicQuery[1], cacheData); - let whereLogicMutable = whereLogic; + const { logic: left, where: whereLogic } = extractLogicCypher(logicQuery[1], cacheData); switch (op) { case '!=': @@ -49,41 +47,38 @@ export function extractLogicCypher(logicQuery: AnyStatement, cacheData: QueryCac op = '=~'; break; case 'isempty': - return [`(${left} IS NULL OR ${left} = "")`, whereLogicMutable]; + return { logic: `(${left} IS NULL OR ${left} = "")`, where: whereLogic }; case 'lower': - return [`toLower(${left})`, whereLogicMutable]; + return { logic: `toLower(${left})`, where: whereLogic }; case 'upper': - return [`toUpper(${left})`, whereLogicMutable]; + return { logic: `toUpper(${left})`, where: whereLogic }; case 'avg': case 'count': case 'max': case 'min': case 'sum': - return createWhereLogic(op, left, whereLogicMutable, cacheData); + return createWhereLogic(op, left, whereLogic, cacheData); } if (logicQuery.length > 2 && logicQuery[2]) { - const [right, whereLogicRight] = extractLogicCypher(logicQuery[2], cacheData); - let rightMutable = right; + const { logic: rightLogic, where: whereLogicRight } = extractLogicCypher(logicQuery[2], cacheData); + let rightMutable = rightLogic; - if (whereLogicRight) { - if (whereLogicMutable) { - whereLogicMutable += ', '; - } - whereLogicMutable += whereLogicRight; + for (const where of whereLogicRight) { + whereLogic.add(where); } if (op === '=~') { rightMutable = `(".*" + ${rightMutable} + ".*")`; } - return [`(${left} ${op} ${rightMutable})`, whereLogicMutable]; + return { logic: `(${left} ${op} ${rightMutable})`, where: whereLogic }; } - return [`(${op} ${left})`, whereLogicMutable]; + return { logic: `(${op} ${left})`, where: whereLogic }; } - return [logicQuery, '']; + return { logic: logicQuery, where: new Set() }; case 'string': - return [logicQuery.replace('@', ''), '']; + return { logic: logicQuery.replace('@', ''), where: new Set() }; case 'number': - return [logicQuery.toString(), '']; + return { logic: logicQuery.toString(), where: new Set() }; default: - return [logicQuery as any, '']; + return { logic: logicQuery as any, where: new Set() }; } } diff --git a/src/utils/cypher/converter/queryConverter.test.ts b/src/utils/cypher/converter/queryConverter.test.ts index 90b0a508a5e1992aaf1d5b6886ec77affa131fcc..f65b5c5815c445f0127cb1892fbbe0e75fe5ee20 100644 --- a/src/utils/cypher/converter/queryConverter.test.ts +++ b/src/utils/cypher/converter/queryConverter.test.ts @@ -313,12 +313,11 @@ describe('query2Cypher', () => { const cypher = query2Cypher(query); const expectedCypher = `MATCH path1 = ((p1:Person)-[acted:ACTED_IN*1..1]->(movie:Movie)) - MATCH path2 = ((p2:Person)-[acted:ACTED_IN*1..1]->(movie:Movie)) - WITH avg(p1.age) AS p1_age_avg, p2, movie - MATCH path1 = ((p1:Person)-[acted:ACTED_IN*1..1]->(movie:Movie)) - MATCH path2 = ((p2:Person)-[acted:ACTED_IN*1..1]->(movie:Movie)) - WHERE (p1.age < p1_age_avg) - RETURN * LIMIT 5000`; + MATCH path2 = ((p2:Person)-[acted:ACTED_IN*1..1]->(movie:Movie)) + WITH avg(p1.age) AS p1_age_avg, p1, movie, p2 + MATCH path1 = ((p1:Person)-[acted:ACTED_IN*1..1]->(movie:Movie)) + MATCH path2 = ((p2:Person)-[acted:ACTED_IN*1..1]->(movie:Movie)) + WHERE (p1.age < p1_age_avg) RETURN * LIMIT 5000`; expect(fixCypherSpaces(cypher)).toBe(fixCypherSpaces(expectedCypher)); }); diff --git a/src/utils/cypher/converter/queryConverter.ts b/src/utils/cypher/converter/queryConverter.ts index 8c593a8efbd4c8319a544729229430a372f73b72..b127f76f5481aacd8fa109329f50e893ae2ffb46 100644 --- a/src/utils/cypher/converter/queryConverter.ts +++ b/src/utils/cypher/converter/queryConverter.ts @@ -48,7 +48,7 @@ export function query2Cypher(JSONQuery: BackendQueryFormat): string | null { // logic calculation for WHERE block if (JSONQuery.logic) { - const [_logic, whereLogic] = extractLogicCypher(JSONQuery.logic, cacheData); + const { logic: _logic, where: whereLogic } = extractLogicCypher(JSONQuery.logic, cacheData); let logic = _logic as string; @@ -62,8 +62,8 @@ export function query2Cypher(JSONQuery: BackendQueryFormat): string | null { } } let totalQueryWithLogic = totalQuery; - if (whereLogic) { - totalQueryWithLogic += `WITH ${whereLogic}\n`; + if (whereLogic && whereLogic.size > 0) { + totalQueryWithLogic += `WITH ${[...whereLogic].join(', ')}\n`; totalQuery = totalQueryWithLogic + totalQuery; } totalQuery += `WHERE ${logic}\n`; diff --git a/src/utils/queryPublisher.ts b/src/utils/queryPublisher.ts index 5df23b68386ba919151d432d9c8cb60e223241a6..ca17e2f169c136a5f7f73df401a68fe086749eb0 100644 --- a/src/utils/queryPublisher.ts +++ b/src/utils/queryPublisher.ts @@ -1,6 +1,6 @@ import { wsReturnKey, type BackendMessageHeader, type MachineLearning, type ToMLMessage } from 'ts-common'; import { log } from '../logger'; -import type { GraphQueryResultFromBackend } from 'ts-common/src/model/webSocket/graphResult'; +import type { GraphQueryResultFromBackend, GraphQueryResultMetaFromBackend } from 'ts-common/src/model/webSocket/graphResult'; import type { RabbitMqBroker } from 'ts-common/rabbitMq'; export class QueryPublisher { @@ -84,7 +84,7 @@ export class QueryPublisher { ); } - publishResultToFrontend(result: GraphQueryResultFromBackend) { + publishResultToFrontend(result: GraphQueryResultMetaFromBackend) { if (!this.headers || !this.routingKey || !this.queryID) { throw new Error('Headers or RoutingKey or queryID not set'); }