* Creates a function which takes the array of values returned by the graph traversal * and returns an object with the values mapped to the variable names in the RETURN clause. * * When multiple properties from the same variable are returned (e.g., `RETURN c.name, c.description`), * they are grou
( ast: Query | UnionQuery | MultiStatement, )
| 175 | * @param ast The AST of the query (Query, UnionQuery, or MultiStatement). |
| 176 | */ |
| 177 | function createPostprocessor( |
| 178 | ast: Query | UnionQuery | MultiStatement, |
| 179 | ): (row: readonly unknown[]) => Record<string, unknown> { |
| 180 | // For multi-statement queries, pass through results as-is |
| 181 | // (each statement has different schema, results include _statementIndex) |
| 182 | if (ast.type === "MultiStatement") { |
| 183 | return (row) => { |
| 184 | if (row && typeof row === "object" && !Array.isArray(row)) { |
| 185 | return row as unknown as Record<string, unknown>; |
| 186 | } |
| 187 | return { _value: row }; |
| 188 | }; |
| 189 | } |
| 190 | |
| 191 | // For union queries, use the first query's RETURN clause for variable names |
| 192 | // (all queries in a UNION must return the same columns) |
| 193 | const query = ast.type === "UnionQuery" ? ast.queries[0]! : ast; |
| 194 | |
| 195 | // For mutation-only queries without RETURN, return empty object |
| 196 | if (!query.return) { |
| 197 | return () => ({}); |
| 198 | } |
| 199 | |
| 200 | const items = query.return.items; |
| 201 | |
| 202 | // Build a map of output key -> list of {index, property} |
| 203 | // This groups properties that should be merged under the same key |
| 204 | const keyMapping = new Map<string, Array<{ index: number; property: string | undefined }>>(); |
| 205 | |
| 206 | for (let i = 0; i < items.length; i++) { |
| 207 | const item = items[i]!; |
| 208 | // Use alias if present, otherwise use variable name, or generate a key for expressions |
| 209 | let key: string; |
| 210 | if (item.alias) { |
| 211 | key = item.alias; |
| 212 | } else if (item.variable) { |
| 213 | key = item.variable; |
| 214 | } else if (item.expression) { |
| 215 | // For expression-based items without alias, generate a key |
| 216 | key = `expr_${i}`; |
| 217 | } else { |
| 218 | key = `item_${i}`; |
| 219 | } |
| 220 | if (!keyMapping.has(key)) { |
| 221 | keyMapping.set(key, []); |
| 222 | } |
| 223 | keyMapping.get(key)!.push({ index: i, property: item.property }); |
| 224 | } |
| 225 | |
| 226 | return (row) => { |
| 227 | // Normalize row to array for aggregate-only queries that yield raw values |
| 228 | // (e.g., COUNT yields a number, not an array) |
| 229 | const normalizedRow = Array.isArray(row) ? row : [row]; |
| 230 | const result: Record<string, unknown> = {}; |
| 231 | |
| 232 | for (const [key, mappings] of keyMapping) { |
| 233 | if (mappings.length === 1) { |
| 234 | // Single mapping for this key - return value directly |
no test coverage detected