|
| 1 | +// Step 1: Find all nodes containing the specified property |
| 2 | +// Use a subquery to handle both MATCH conditions |
| 3 | +CALL { |
| 4 | + WITH $propertyNames AS propertyNames |
| 5 | + UNWIND propertyNames AS propertyName |
| 6 | + // Match nodes that directly contain the specified property |
| 7 | + MATCH (simple_node) |
| 8 | + WHERE simple_node[propertyName] IS NOT NULL |
| 9 | + RETURN DISTINCT simple_node AS resultNode |
| 10 | +UNION |
| 11 | + WITH $propertyNames AS propertyNames |
| 12 | + UNWIND propertyNames AS propertyName |
| 13 | + // Match nodes that are related via a specific relationship property |
| 14 | + MATCH (nested_node)-[r:PARENT_OF {attribute_name: propertyName}]->(:NESTED_ATTRIBUTE) |
| 15 | + WHERE r.type IN ["list", "dict"] |
| 16 | + RETURN DISTINCT nested_node AS resultNode |
| 17 | +} |
| 18 | + |
| 19 | +// Step 2: Use APOC to find the path from each node to the root, excluding 'NESTED_ATTRIBUTE' nodes |
| 20 | +CALL apoc.path.subgraphNodes(resultNode, { |
| 21 | + relationshipFilter: 'PARENT_OF<', |
| 22 | + labelFilter: '-NESTED_ATTRIBUTE', |
| 23 | + maxLevel: -1 |
| 24 | +}) YIELD node AS pathNode |
| 25 | + |
| 26 | +// Step 3: Collect the nodes along the path and construct the full name |
| 27 | +WITH resultNode, |
| 28 | + COLLECT(pathNode) AS pathNodes |
| 29 | +WITH resultNode, |
| 30 | + REDUCE(fullPath = '', n IN pathNodes | |
| 31 | + CASE |
| 32 | + WHEN fullPath = '' THEN n.node_name |
| 33 | + ELSE n.node_name + '/' + fullPath |
| 34 | + END) AS fullName |
| 35 | +// Step 4: Find nested children with label "NESTED_ATTRIBUTE" and their relationships |
| 36 | +OPTIONAL MATCH (resultNode)-[r:PARENT_OF]->(nestedChild:NESTED_ATTRIBUTE) |
| 37 | +OPTIONAL MATCH (nestedChild)-[nestedRel:PARENT_OF*]->(child:NESTED_ATTRIBUTE) |
| 38 | + |
| 39 | +// Step 5: Return the full path, resultNode, nested children, and relationships |
| 40 | +RETURN DISTINCT |
| 41 | + fullName, |
| 42 | + resultNode, |
| 43 | + COLLECT(DISTINCT nestedChild) AS nestedChildren, |
| 44 | + COLLECT(DISTINCT r) + COLLECT(DISTINCT nestedRel) AS relationships |
| 45 | + |
| 46 | +----------------- |
| 47 | + |
| 48 | +----------------- |
| 49 | +// Get node based on topics. Does not work for # and + entries. need atleast one |
| 50 | +WITH $topics AS inputPaths |
| 51 | +UNWIND inputPaths AS inputPath |
| 52 | +WITH split(inputPath, '/') AS nodeNames, inputPath |
| 53 | + |
| 54 | +// Step 1: Match the root node based on the first part of the path |
| 55 | +MATCH (root {node_name: nodeNames[0]}) |
| 56 | + |
| 57 | +// Step 2: Expand paths from the root node |
| 58 | +CALL apoc.path.expand( |
| 59 | + root, |
| 60 | + 'PARENT_OF>', |
| 61 | + '', // No label filter |
| 62 | + 0, // Min depth 0 to include the root node |
| 63 | + -1 // Max depth is unlimited to cover all possible depths |
| 64 | +) YIELD path AS fullPath |
| 65 | + |
| 66 | +// Step 3: Filter paths according to the inputPath with wildcards |
| 67 | +WITH fullPath, nodeNames, nodes(fullPath) AS nodesPath |
| 68 | +WITH fullPath, nodeNames, nodesPath, last(nodes(fullPath)) AS lastNode |
| 69 | +WHERE |
| 70 | + size(nodesPath) >= size(nodeNames) AND |
| 71 | + all(i IN range(0, size(nodeNames) - 1) WHERE |
| 72 | + (nodeNames[i] = '+' AND size(nodesPath) > i) OR |
| 73 | + (nodeNames[i] = '#' AND size(nodesPath) > i) OR |
| 74 | + (nodeNames[i] = nodesPath[i].node_name) |
| 75 | + ) AND |
| 76 | + (size(nodeNames) = size(nodesPath) OR nodeNames[-1] = '#' OR nodeNames[-1] = lastNode.node_name) |
| 77 | + |
| 78 | +// Step 4: Construct the MQTT topic path from the matched path |
| 79 | +WITH fullPath, lastNode, reduce(fullPathStr = '', n IN nodesPath | |
| 80 | + CASE |
| 81 | + WHEN fullPathStr = '' THEN n.node_name |
| 82 | + ELSE fullPathStr + '/' + n.node_name |
| 83 | + END) AS mqttTopic |
| 84 | + |
| 85 | +// Step 5: Exclude paths containing 'NESTED_ATTRIBUTE' nodes in the path |
| 86 | +WHERE NOT any(n IN nodesPath WHERE 'NESTED_ATTRIBUTE' IN labels(n)) |
| 87 | + |
| 88 | +// Step 6: Match directly connected nested children of the last node that have the label 'NESTED_ATTRIBUTE' |
| 89 | +OPTIONAL MATCH (lastNode)-[r:PARENT_OF]->(nestedChild:NESTED_ATTRIBUTE) |
| 90 | + |
| 91 | +// Step 7: Match any children of the nestedChild if they have the label "NESTED_ATTRIBUTE" |
| 92 | +OPTIONAL MATCH (nestedChild)-[nestedRel:PARENT_OF*]->(child:NESTED_ATTRIBUTE) |
| 93 | + |
| 94 | +// Step 8: Collect results |
| 95 | +WITH DISTINCT mqttTopic, lastNode, |
| 96 | + COLLECT(DISTINCT nestedChild) AS nestedChildren, |
| 97 | + COLLECT(DISTINCT r) + COLLECT(DISTINCT nestedRel) AS relationships |
| 98 | + |
| 99 | +// Step 9: Return the MQTT topic path, the last node, nested children, and relationships |
| 100 | +RETURN |
| 101 | + mqttTopic, |
| 102 | + lastNode as node, |
| 103 | + nestedChildren, |
| 104 | + relationships |
0 commit comments