From b1c4f1f4ff2b1c097908a0854267c9594ad87388 Mon Sep 17 00:00:00 2001
From: Leonardo <leomilho@gmail.com>
Date: Thu, 23 Jan 2025 18:28:11 +0100
Subject: [PATCH] fix: relation based logic query when query is more complex

---
 src/utils/cypher/converter/model.ts           |  1 +
 src/utils/cypher/converter/node.ts            |  9 +-
 .../cypher/converter/queryConverter.test.ts   | 95 +++++++++++++++++++
 src/utils/cypher/converter/queryConverter.ts  |  4 +-
 src/utils/cypher/converter/relation.ts        | 15 ++-
 5 files changed, 108 insertions(+), 16 deletions(-)

diff --git a/src/utils/cypher/converter/model.ts b/src/utils/cypher/converter/model.ts
index e4460ce..e552f45 100644
--- a/src/utils/cypher/converter/model.ts
+++ b/src/utils/cypher/converter/model.ts
@@ -11,4 +11,5 @@ export interface EntityCacheData {
 export interface QueryCacheData {
   entities: EntityCacheData[];
   relations: RelationCacheData[];
+  unwinds: string[];
 }
diff --git a/src/utils/cypher/converter/node.ts b/src/utils/cypher/converter/node.ts
index 0837633..091bb84 100644
--- a/src/utils/cypher/converter/node.ts
+++ b/src/utils/cypher/converter/node.ts
@@ -2,7 +2,7 @@ import type { NodeStruct } from 'ts-common';
 import type { QueryCacheData } from './model';
 import { getRelationCypher } from './relation';
 
-export function getNodeCypher(JSONQuery: NodeStruct): [string, QueryCacheData] {
+export function getNodeCypher(JSONQuery: NodeStruct, cacheData: QueryCacheData): [string, QueryCacheData] {
   let label = '';
   if (JSONQuery.label) {
     label = `:${JSONQuery.label}`;
@@ -12,8 +12,6 @@ export function getNodeCypher(JSONQuery: NodeStruct): [string, QueryCacheData] {
     id = JSONQuery.id;
   }
 
-  const cacheData: QueryCacheData = { entities: [], relations: [] };
-
   if (id) {
     cacheData.entities.push({ id, queryId: '' });
   }
@@ -21,11 +19,9 @@ export function getNodeCypher(JSONQuery: NodeStruct): [string, QueryCacheData] {
   let cypher = `(${id}${label})`;
 
   if (JSONQuery.relation) {
-    const [relationCypher, subCache] = getRelationCypher(JSONQuery.relation);
+    const [relationCypher, _cacheData] = getRelationCypher(JSONQuery.relation, cacheData);
 
     if (relationCypher) {
-      cacheData.entities.push(...subCache.entities);
-      cacheData.relations.push(...subCache.relations);
       if (JSONQuery.relation.direction === 'FROM') {
         cypher += `<-${relationCypher}`;
       } else if (JSONQuery.relation.direction === 'TO') {
@@ -34,6 +30,7 @@ export function getNodeCypher(JSONQuery: NodeStruct): [string, QueryCacheData] {
         cypher += `-${relationCypher}`;
       }
     }
+    return [cypher, _cacheData];
   }
   return [cypher, cacheData];
 }
diff --git a/src/utils/cypher/converter/queryConverter.test.ts b/src/utils/cypher/converter/queryConverter.test.ts
index bb3cd7e..90b0a50 100644
--- a/src/utils/cypher/converter/queryConverter.test.ts
+++ b/src/utils/cypher/converter/queryConverter.test.ts
@@ -532,4 +532,99 @@ describe('query2Cypher', () => {
 
     expect(fixCypherSpaces(cypher)).toBe(fixCypherSpaces(expectedCypher));
   });
+
+  it('should return correctly on a query with boolean logic in relation', () => {
+    const query: BackendQueryFormat = {
+      saveStateID: '31966faf-0741-4a70-b92b-62c4b5e25d91',
+      query: [
+        {
+          id: 'path_0',
+          node: {
+            id: 'id_1737115397734',
+            label: 'Product',
+            relation: {
+              id: 'id_1737115397848',
+              label: 'PRODUCT_MATERIAL',
+              depth: {
+                max: 1,
+                min: 1,
+              },
+              direction: 'TO',
+              node: {
+                id: 'id_1737115397423',
+                label: 'Material',
+              },
+            },
+          },
+        },
+        {
+          id: 'path_1',
+          node: {
+            id: 'id_1737115397734',
+            label: 'Product',
+            relation: {
+              id: 'id_1737115611667',
+              label: 'DEPARTS_FROM',
+              depth: {
+                max: 1,
+                min: 1,
+              },
+              direction: 'TO',
+              node: {
+                id: 'id_1737115612952',
+                label: 'Country',
+                relation: {
+                  id: 'id_1737115886717',
+                  label: 'CountryFlow',
+                  depth: {
+                    max: 1,
+                    min: 1,
+                  },
+                  direction: 'TO',
+                  node: {
+                    id: 'id_1737115684010',
+                    label: 'Country',
+                  },
+                },
+              },
+            },
+          },
+        },
+        {
+          id: 'path_2',
+          node: {
+            id: 'id_1737115397734',
+            label: 'Product',
+            relation: {
+              id: 'id_1737115682374',
+              label: 'ARRIVES_AT',
+              depth: {
+                max: 1,
+                min: 1,
+              },
+              direction: 'TO',
+              node: {
+                id: 'id_1737115684010',
+                label: 'Country',
+              },
+            },
+          },
+        },
+      ],
+      machineLearning: [],
+      limit: 100,
+      return: ['*'],
+      cached: false,
+      logic: ['And', ['==', '@id_1737115397423.GO', '"852349"'], ['==', '@id_1737115886717.goodFlow', 'false']],
+    };
+
+    const cypher = query2Cypher(query);
+    const expectedCypher = `MATCH path_0 = ((id_1737115397734:Product)-[id_1737115397848:PRODUCT_MATERIAL*1..1]->(id_1737115397423:Material)) 
+    MATCH path_1 = ((id_1737115397734:Product)-[id_1737115611667:DEPARTS_FROM*1..1]->(id_1737115612952:Country)-[id_1737115886717:CountryFlow*1..1]->(id_1737115684010:Country))
+    MATCH path_2 = ((id_1737115397734:Product)-[id_1737115682374:ARRIVES_AT*1..1]->(id_1737115684010:Country))
+    WHERE ALL(path_2_rel_id_1737115886717 in id_1737115886717 WHERE ((id_1737115397423.GO = "852349") and
+    (path_2_rel_id_1737115886717.goodFlow = false))) RETURN * LIMIT 100`;
+
+    expect(fixCypherSpaces(cypher)).toEqual(fixCypherSpaces(expectedCypher));
+  });
 });
diff --git a/src/utils/cypher/converter/queryConverter.ts b/src/utils/cypher/converter/queryConverter.ts
index 240da9b..8c593a8 100644
--- a/src/utils/cypher/converter/queryConverter.ts
+++ b/src/utils/cypher/converter/queryConverter.ts
@@ -13,12 +13,12 @@ import { getNodeCypher } from './node';
 export function query2Cypher(JSONQuery: BackendQueryFormat): string | null {
   let totalQuery = '';
   let matchQuery = '';
-  let cacheData: QueryCacheData = { entities: [], relations: [] };
+  let cacheData: QueryCacheData = { entities: [], relations: [], unwinds: [] };
 
   // MATCH block
   for (const query of JSONQuery.query) {
     let match = 'MATCH ';
-    const [nodeCypher, _cacheData] = getNodeCypher(query.node);
+    const [nodeCypher, _cacheData] = getNodeCypher(query.node, cacheData);
 
     cacheData = _cacheData;
     for (let i = 0; i < cacheData.entities.length; i++) {
diff --git a/src/utils/cypher/converter/relation.ts b/src/utils/cypher/converter/relation.ts
index 9689a60..0cc8da4 100644
--- a/src/utils/cypher/converter/relation.ts
+++ b/src/utils/cypher/converter/relation.ts
@@ -4,8 +4,9 @@ import type { QueryCacheData } from './model';
 
 export const NoNodeError = new Error('relation must have a node');
 
-export function getRelationCypher(JSONQuery: RelationStruct): [string | undefined, QueryCacheData] {
+export function getRelationCypher(JSONQuery: RelationStruct, cacheData: QueryCacheData): [string | undefined, QueryCacheData] {
   let label = '';
+
   if (JSONQuery.label) {
     label = `:${JSONQuery.label}`;
   }
@@ -14,12 +15,13 @@ export function getRelationCypher(JSONQuery: RelationStruct): [string | undefine
     id = JSONQuery.id;
   }
   let depth = '';
+
   if (JSONQuery.depth) {
+    //  && JSONQuery.depth.min !== 1 && JSONQuery.depth.max !== 1
     depth = `*${JSONQuery.depth.min}..${JSONQuery.depth.max}`;
+    cacheData.unwinds.push(id + '_unwind');
   }
 
-  const cacheData: QueryCacheData = { entities: [], relations: [] };
-
   if (id) {
     cacheData.relations.push({ id, queryId: '' });
   }
@@ -29,11 +31,8 @@ export function getRelationCypher(JSONQuery: RelationStruct): [string | undefine
   if (!JSONQuery.node) {
     return [undefined, cacheData];
   }
+  const [nodeCypher, _cacheData] = getNodeCypher(JSONQuery.node, cacheData);
 
-  const [nodeCypher, subCache] = getNodeCypher(JSONQuery.node);
-
-  cacheData.entities.push(...subCache.entities);
-  cacheData.relations.push(...subCache.relations);
   if (JSONQuery.direction === 'TO') {
     cypher += `->${nodeCypher}`;
   } else if (JSONQuery.direction === 'FROM') {
@@ -41,5 +40,5 @@ export function getRelationCypher(JSONQuery: RelationStruct): [string | undefine
   } else {
     cypher += `-${nodeCypher}`;
   }
-  return [cypher, cacheData];
+  return [cypher, _cacheData];
 }
-- 
GitLab