* Extract variable names bound by a pattern for OPTIONAL MATCH null bindings. * When skipAnchor is true, skips the first node if it's an anchor pattern * (variable reference with no labels and has edges following).
( pattern: Pattern | MultiPattern | ShortestPathPattern, skipAnchor: boolean = false, )
| 2950 | * (variable reference with no labels and has edges following). |
| 2951 | */ |
| 2952 | function extractPatternVariables( |
| 2953 | pattern: Pattern | MultiPattern | ShortestPathPattern, |
| 2954 | skipAnchor: boolean = false, |
| 2955 | ): string[] { |
| 2956 | const variables: string[] = []; |
| 2957 | |
| 2958 | if (pattern.type === "ShortestPathPattern") { |
| 2959 | const sp = pattern as ShortestPathPattern; |
| 2960 | if (sp.variable) variables.push(sp.variable); |
| 2961 | if (sp.source.variable) variables.push(sp.source.variable); |
| 2962 | if (sp.target.variable) variables.push(sp.target.variable); |
| 2963 | if (sp.edge.variable) variables.push(sp.edge.variable); |
| 2964 | } else if (pattern.type === "MultiPattern") { |
| 2965 | const mp = pattern as MultiPattern; |
| 2966 | for (const subPattern of mp.patterns) { |
| 2967 | variables.push(...extractPatternVariables(subPattern, skipAnchor)); |
| 2968 | } |
| 2969 | } else { |
| 2970 | // Regular Pattern |
| 2971 | const p = pattern as Pattern; |
| 2972 | let isFirst = true; |
| 2973 | for (const element of p.elements) { |
| 2974 | if (element.type === "NodePattern") { |
| 2975 | const node = element as NodePattern; |
| 2976 | // Skip the anchor variable if requested and it looks like an anchor |
| 2977 | // (first node with variable but no labels/expression, with more elements following) |
| 2978 | const isAnchor = |
| 2979 | isFirst && |
| 2980 | skipAnchor && |
| 2981 | node.variable && |
| 2982 | node.labels.length === 0 && |
| 2983 | !node.labelExpression && |
| 2984 | !node.properties && |
| 2985 | p.elements.length > 1; |
| 2986 | if (node.variable && !isAnchor) { |
| 2987 | variables.push(node.variable); |
| 2988 | } |
| 2989 | isFirst = false; |
| 2990 | } else if (element.type === "EdgePattern") { |
| 2991 | const edge = element as EdgePattern; |
| 2992 | if (edge.variable) variables.push(edge.variable); |
| 2993 | isFirst = false; |
| 2994 | } else if (element.type === "ParenthesizedPathPattern") { |
| 2995 | // Recursively extract variables from the inner pattern |
| 2996 | const parenthesized = element as ParenthesizedPathPattern; |
| 2997 | variables.push(...extractPatternVariables(parenthesized.pattern, false)); |
| 2998 | isFirst = false; |
| 2999 | } |
| 3000 | } |
| 3001 | } |
| 3002 | |
| 3003 | return variables; |
| 3004 | } |
| 3005 | |
| 3006 | /** |
| 3007 | * Alias information from RETURN clause items. |
no test coverage detected