Skip to content

Commit f393f3c

Browse files
authored
Merge pull request #42 from microsoft/unwind_bug_fix
Unwind bug fix
2 parents 2001fbb + 8ba7a9d commit f393f3c

File tree

6 files changed

+74
-15
lines changed

6 files changed

+74
-15
lines changed

flowquery-py/src/parsing/operations/unwind.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ def as_(self) -> Any:
2525

2626
async def run(self) -> None:
2727
expression_value = self.expression.value()
28+
if expression_value is None:
29+
if self.next:
30+
self.next.reset()
31+
return
2832
if not isinstance(expression_value, list):
2933
raise ValueError("Expected array")
3034
for item in expression_value:

flowquery-py/tests/compute/test_runner.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,29 @@ async def test_aggregated_with_and_return(self):
474474
assert results[0] == {"i": 1, "sum": 12}
475475
assert results[1] == {"i": 2, "sum": 12}
476476

477+
@pytest.mark.asyncio
478+
async def test_unwind_null_produces_zero_rows(self):
479+
"""Test that UNWIND null produces zero rows (Neo4j-compatible)."""
480+
runner = Runner("WITH null AS x UNWIND x AS i RETURN i")
481+
await runner.run()
482+
results = runner.results
483+
assert len(results) == 0
484+
485+
@pytest.mark.asyncio
486+
async def test_unwind_null_in_pipeline_preserves_no_rows(self):
487+
"""Test that UNWIND null stops the pipeline producing no rows."""
488+
runner = Runner(
489+
"""
490+
WITH null AS arr
491+
UNWIND arr AS i
492+
UNWIND [1, 2] AS j
493+
RETURN i, j
494+
"""
495+
)
496+
await runner.run()
497+
results = runner.results
498+
assert len(results) == 0
499+
477500
@pytest.mark.asyncio
478501
async def test_aggregated_with_on_empty_result_set(self):
479502
"""Test aggregated with on empty result set does not crash."""

jest.config.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,11 @@ module.exports = {
44
transform: {
55
"^.+.tsx?$": ["ts-jest", {}],
66
},
7-
testPathIgnorePatterns: ["/node_modules/", "/flowquery-vscode/test/", "/misc/apps/"],
7+
testPathIgnorePatterns: [
8+
"/node_modules/",
9+
"/flowquery-py/",
10+
"/flowquery-vscode/",
11+
"/docs/",
12+
"/misc/",
13+
],
814
};

src/parsing/operations/unwind.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import Operation from "./operation";
21
import Expression from "../expressions/expression";
2+
import Operation from "./operation";
33

44
class Unwind extends Operation {
55
private _value: any;
@@ -15,10 +15,14 @@ class Unwind extends Operation {
1515
}
1616
public async run(): Promise<void> {
1717
const expression = this.expression.value();
18-
if(!(Array.isArray(expression))) {
19-
throw new Error('Expected array');
18+
if (expression === null || expression === undefined) {
19+
this.next?.reset();
20+
return;
21+
}
22+
if (!Array.isArray(expression)) {
23+
throw new Error("Expected array");
2024
}
21-
for(let i = 0; i < expression.length; i++) {
25+
for (let i = 0; i < expression.length; i++) {
2226
this._value = expression[i];
2327
await this.next?.run();
2428
}
@@ -29,4 +33,4 @@ class Unwind extends Operation {
2933
}
3034
}
3135

32-
export default Unwind;
36+
export default Unwind;

tests/compute/runner.test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,27 @@ test("Test aggregated with and return", async () => {
415415
expect(results[1]).toEqual({ i: 2, sum: 12 });
416416
});
417417

418+
test("Test unwind null produces zero rows", async () => {
419+
const runner = new Runner("WITH null AS x UNWIND x AS i RETURN i");
420+
await runner.run();
421+
const results = runner.results;
422+
expect(results.length).toBe(0);
423+
});
424+
425+
test("Test unwind null in pipeline preserves no rows", async () => {
426+
const runner = new Runner(
427+
`
428+
WITH null AS arr
429+
UNWIND arr AS i
430+
UNWIND [1, 2] AS j
431+
RETURN i, j
432+
`
433+
);
434+
await runner.run();
435+
const results = runner.results;
436+
expect(results.length).toBe(0);
437+
});
438+
418439
test("Test aggregated with on empty result set", async () => {
419440
const runner = new Runner(
420441
`

tsconfig.json

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,17 @@
33
/* Visit https://aka.ms/tsconfig to read more about this file */
44
"outDir": "./dist",
55
"rootDir": "./src", // Optional, but recommended to specify the root directory
6-
"target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ /* Modules */
7-
"experimentalDecorators": true, /* Enable experimental support for decorators. */
8-
"module": "commonjs", /* Specify what module code is generated. */
9-
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
10-
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
6+
"target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ /* Modules */,
7+
"experimentalDecorators": true /* Enable experimental support for decorators. */,
8+
"module": "commonjs" /* Specify what module code is generated. */,
9+
"esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */,
10+
"forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
1111
"strict": true,
1212
"types": ["node", "jest"],
1313
"sourceMap": true,
14-
"declaration": true, /* Generate .d.ts declaration files. */
15-
"declarationMap": true /* Generate sourcemaps for .d.ts files. */
14+
"declaration": true /* Generate .d.ts declaration files. */,
15+
"declarationMap": true /* Generate sourcemaps for .d.ts files. */
1616
},
17-
"include": ["src"]
18-
}
17+
"include": ["src"],
18+
"exclude": ["flowquery-py", "flowquery-vscode", "docs", "misc"]
19+
}

0 commit comments

Comments
 (0)