* Convert a pattern into a sequence of steps. * Handles node patterns, edge patterns, and variable-length paths. * * @param pattern The pattern to convert * @param whereClause Optional WHERE clause for filtering * @param anchoredToInput If true, the first node is a reference to an existing bind
( pattern: Pattern, whereClause?: WhereClause, anchoredToInput: boolean = false, preservePriorBindings: boolean = false, )
| 1375 | * to preserve variable bindings from a prior WITH clause. |
| 1376 | */ |
| 1377 | function convertPattern( |
| 1378 | pattern: Pattern, |
| 1379 | whereClause?: WhereClause, |
| 1380 | anchoredToInput: boolean = false, |
| 1381 | preservePriorBindings: boolean = false, |
| 1382 | ): Step<any>[] { |
| 1383 | const steps: Step<any>[] = []; |
| 1384 | const elements = pattern.elements; |
| 1385 | |
| 1386 | if (elements.length === 0) { |
| 1387 | throw new Error("Pattern must have at least one element"); |
| 1388 | } |
| 1389 | |
| 1390 | // First element should be a NodePattern |
| 1391 | const firstElement = elements[0]; |
| 1392 | if (firstElement?.type !== "NodePattern") { |
| 1393 | throw new Error("Pattern must start with a NodePattern"); |
| 1394 | } |
| 1395 | |
| 1396 | const firstNode = firstElement as NodePattern; |
| 1397 | const stepLabels = firstNode.variable ? [firstNode.variable] : undefined; |
| 1398 | |
| 1399 | // Determine if this is an anchored pattern (variable reference to existing binding) |
| 1400 | // An anchored pattern has a variable but no labels and has more elements (edges) |
| 1401 | // In this case, we traverse from the input path rather than fetching all vertices. |
| 1402 | // |
| 1403 | // The heuristic only applies when anchoredToInput is explicitly true (passed from caller). |
| 1404 | // This handles cases like EXISTS { (n)-[:KNOWS]->(m) } where `n` is a bound variable. |
| 1405 | // |
| 1406 | // For fresh MATCH clauses, anchoredToInput is false, and we always create FetchVertices |
| 1407 | // even if the pattern looks like it could be anchored (e.g., MATCH (a)-[:knows]->(b)). |
| 1408 | // The label/property filter can come from the WHERE clause (e.g., WHERE a IS :Person). |
| 1409 | const isAnchorPattern = |
| 1410 | anchoredToInput && |
| 1411 | firstNode.variable && |
| 1412 | firstNode.labels.length === 0 && |
| 1413 | !firstNode.labelExpression && |
| 1414 | elements.length > 1 && |
| 1415 | !firstNode.properties; |
| 1416 | |
| 1417 | if (!isAnchorPattern) { |
| 1418 | // Create initial fetch step for pattern match |
| 1419 | // Use CartesianFetchStep if we need to preserve bindings from a prior WITH clause |
| 1420 | if (preservePriorBindings) { |
| 1421 | steps.push( |
| 1422 | new CartesianFetchStep({ |
| 1423 | vertexLabels: firstNode.labels.length > 0 ? firstNode.labels : undefined, |
| 1424 | stepLabels, |
| 1425 | }), |
| 1426 | ); |
| 1427 | } else { |
| 1428 | steps.push( |
| 1429 | new FetchVerticesStep({ |
| 1430 | vertexLabels: firstNode.labels.length > 0 ? firstNode.labels : undefined, |
| 1431 | stepLabels, |
| 1432 | }), |
| 1433 | ); |
| 1434 | } |
no test coverage detected