flowquery 1.0.41 → 1.0.42
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/flowquery.min.js +1 -1
- package/dist/graph/node_reference.d.ts.map +1 -1
- package/dist/graph/node_reference.js +7 -3
- package/dist/graph/node_reference.js.map +1 -1
- package/docs/flowquery.min.js +1 -1
- package/flowquery-py/pyproject.toml +1 -1
- package/flowquery-py/src/graph/node_reference.py +5 -4
- package/flowquery-py/tests/compute/test_runner.py +158 -1
- package/flowquery-vscode/flowQueryEngine/flowquery.min.js +1 -1
- package/package.json +1 -1
- package/src/graph/node_reference.ts +5 -1
- package/tests/compute/runner.test.ts +142 -0
package/package.json
CHANGED
|
@@ -16,7 +16,11 @@ class NodeReference extends Node {
|
|
|
16
16
|
return this._reference;
|
|
17
17
|
}
|
|
18
18
|
public async next(): Promise<void> {
|
|
19
|
-
this.
|
|
19
|
+
const referenced = this._reference?.value();
|
|
20
|
+
if (referenced == null) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
this.setValue(referenced);
|
|
20
24
|
await this._outgoing?.find(this._value!.id);
|
|
21
25
|
await this.runTodoNext();
|
|
22
26
|
}
|
|
@@ -4097,3 +4097,145 @@ test("Test RETURN alias shadowing graph variable in same RETURN clause", async (
|
|
|
4097
4097
|
mentorDepartment: "Engineering",
|
|
4098
4098
|
});
|
|
4099
4099
|
});
|
|
4100
|
+
|
|
4101
|
+
test("Test chained optional match with null intermediate node", async () => {
|
|
4102
|
+
// Create a chain: A -> B -> C (C has no outgoing REPORTS_TO)
|
|
4103
|
+
await new Runner(`
|
|
4104
|
+
CREATE VIRTUAL (:Employee) AS {
|
|
4105
|
+
unwind [
|
|
4106
|
+
{id: 1, name: 'Alice'},
|
|
4107
|
+
{id: 2, name: 'Bob'},
|
|
4108
|
+
{id: 3, name: 'Charlie'}
|
|
4109
|
+
] as record
|
|
4110
|
+
RETURN record.id as id, record.name as name
|
|
4111
|
+
}
|
|
4112
|
+
`).run();
|
|
4113
|
+
await new Runner(`
|
|
4114
|
+
CREATE VIRTUAL (:Employee)-[:REPORTS_TO]-(:Employee) AS {
|
|
4115
|
+
unwind [
|
|
4116
|
+
{left_id: 1, right_id: 2},
|
|
4117
|
+
{left_id: 2, right_id: 3}
|
|
4118
|
+
] as record
|
|
4119
|
+
RETURN record.left_id as left_id, record.right_id as right_id
|
|
4120
|
+
}
|
|
4121
|
+
`).run();
|
|
4122
|
+
|
|
4123
|
+
// Alice -> Bob -> Charlie -> null -> null
|
|
4124
|
+
// m1=Bob, m2=Charlie, m3=null, m4=null (should not crash)
|
|
4125
|
+
const runner = new Runner(`
|
|
4126
|
+
MATCH (u:Employee)
|
|
4127
|
+
WHERE u.name = "Alice"
|
|
4128
|
+
OPTIONAL MATCH (u)-[:REPORTS_TO]->(m1:Employee)
|
|
4129
|
+
OPTIONAL MATCH (m1)-[:REPORTS_TO]->(m2:Employee)
|
|
4130
|
+
OPTIONAL MATCH (m2)-[:REPORTS_TO]->(m3:Employee)
|
|
4131
|
+
OPTIONAL MATCH (m3)-[:REPORTS_TO]->(m4:Employee)
|
|
4132
|
+
RETURN
|
|
4133
|
+
u.name AS user,
|
|
4134
|
+
m1.name AS manager1,
|
|
4135
|
+
m2.name AS manager2,
|
|
4136
|
+
m3.name AS manager3,
|
|
4137
|
+
m4.name AS manager4
|
|
4138
|
+
`);
|
|
4139
|
+
await runner.run();
|
|
4140
|
+
const results = runner.results;
|
|
4141
|
+
|
|
4142
|
+
expect(results.length).toBe(1);
|
|
4143
|
+
expect(results[0].user).toBe("Alice");
|
|
4144
|
+
expect(results[0].manager1).toBe("Bob");
|
|
4145
|
+
expect(results[0].manager2).toBe("Charlie");
|
|
4146
|
+
expect(results[0].manager3).toBeNull();
|
|
4147
|
+
expect(results[0].manager4).toBeNull();
|
|
4148
|
+
});
|
|
4149
|
+
|
|
4150
|
+
test("Test chained optional match all null from first optional", async () => {
|
|
4151
|
+
// Create nodes with no relationships
|
|
4152
|
+
await new Runner(`
|
|
4153
|
+
CREATE VIRTUAL (:Worker) AS {
|
|
4154
|
+
unwind [
|
|
4155
|
+
{id: 1, name: 'Solo'}
|
|
4156
|
+
] as record
|
|
4157
|
+
RETURN record.id as id, record.name as name
|
|
4158
|
+
}
|
|
4159
|
+
`).run();
|
|
4160
|
+
await new Runner(`
|
|
4161
|
+
CREATE VIRTUAL (:Worker)-[:MANAGES]-(:Worker) AS {
|
|
4162
|
+
unwind [] as record
|
|
4163
|
+
RETURN record.left_id as left_id, record.right_id as right_id
|
|
4164
|
+
}
|
|
4165
|
+
`).run();
|
|
4166
|
+
|
|
4167
|
+
// Solo has no MANAGES relationship at all
|
|
4168
|
+
// m1=null, m2=null, m3=null
|
|
4169
|
+
const runner = new Runner(`
|
|
4170
|
+
MATCH (u:Worker)
|
|
4171
|
+
OPTIONAL MATCH (u)-[:MANAGES]->(m1:Worker)
|
|
4172
|
+
OPTIONAL MATCH (m1)-[:MANAGES]->(m2:Worker)
|
|
4173
|
+
OPTIONAL MATCH (m2)-[:MANAGES]->(m3:Worker)
|
|
4174
|
+
RETURN
|
|
4175
|
+
u.name AS user,
|
|
4176
|
+
m1.name AS mgr1,
|
|
4177
|
+
m2.name AS mgr2,
|
|
4178
|
+
m3.name AS mgr3
|
|
4179
|
+
`);
|
|
4180
|
+
await runner.run();
|
|
4181
|
+
const results = runner.results;
|
|
4182
|
+
|
|
4183
|
+
expect(results.length).toBe(1);
|
|
4184
|
+
expect(results[0].user).toBe("Solo");
|
|
4185
|
+
expect(results[0].mgr1).toBeNull();
|
|
4186
|
+
expect(results[0].mgr2).toBeNull();
|
|
4187
|
+
expect(results[0].mgr3).toBeNull();
|
|
4188
|
+
});
|
|
4189
|
+
|
|
4190
|
+
test("Test chained optional match with mixed null and non-null paths", async () => {
|
|
4191
|
+
// Two starting nodes: one with full chain, one with partial
|
|
4192
|
+
await new Runner(`
|
|
4193
|
+
CREATE VIRTUAL (:Staff) AS {
|
|
4194
|
+
unwind [
|
|
4195
|
+
{id: 1, name: 'Dev'},
|
|
4196
|
+
{id: 2, name: 'Lead'},
|
|
4197
|
+
{id: 3, name: 'Director'},
|
|
4198
|
+
{id: 4, name: 'Intern'}
|
|
4199
|
+
] as record
|
|
4200
|
+
RETURN record.id as id, record.name as name
|
|
4201
|
+
}
|
|
4202
|
+
`).run();
|
|
4203
|
+
await new Runner(`
|
|
4204
|
+
CREATE VIRTUAL (:Staff)-[:REPORTS_TO]-(:Staff) AS {
|
|
4205
|
+
unwind [
|
|
4206
|
+
{left_id: 1, right_id: 2},
|
|
4207
|
+
{left_id: 2, right_id: 3}
|
|
4208
|
+
] as record
|
|
4209
|
+
RETURN record.left_id as left_id, record.right_id as right_id
|
|
4210
|
+
}
|
|
4211
|
+
`).run();
|
|
4212
|
+
|
|
4213
|
+
// Dev -> Lead -> Director -> null
|
|
4214
|
+
// Intern -> null -> null -> null
|
|
4215
|
+
const runner = new Runner(`
|
|
4216
|
+
MATCH (u:Staff)
|
|
4217
|
+
WHERE u.name = "Dev" OR u.name = "Intern"
|
|
4218
|
+
OPTIONAL MATCH (u)-[:REPORTS_TO]->(m1:Staff)
|
|
4219
|
+
OPTIONAL MATCH (m1)-[:REPORTS_TO]->(m2:Staff)
|
|
4220
|
+
OPTIONAL MATCH (m2)-[:REPORTS_TO]->(m3:Staff)
|
|
4221
|
+
RETURN
|
|
4222
|
+
u.name AS user,
|
|
4223
|
+
m1.name AS mgr1,
|
|
4224
|
+
m2.name AS mgr2,
|
|
4225
|
+
m3.name AS mgr3
|
|
4226
|
+
`);
|
|
4227
|
+
await runner.run();
|
|
4228
|
+
const results = runner.results;
|
|
4229
|
+
|
|
4230
|
+
expect(results.length).toBe(2);
|
|
4231
|
+
// Dev's chain
|
|
4232
|
+
const dev = results.find((r: any) => r.user === "Dev");
|
|
4233
|
+
expect(dev.mgr1).toBe("Lead");
|
|
4234
|
+
expect(dev.mgr2).toBe("Director");
|
|
4235
|
+
expect(dev.mgr3).toBeNull();
|
|
4236
|
+
// Intern's chain
|
|
4237
|
+
const intern = results.find((r: any) => r.user === "Intern");
|
|
4238
|
+
expect(intern.mgr1).toBeNull();
|
|
4239
|
+
expect(intern.mgr2).toBeNull();
|
|
4240
|
+
expect(intern.mgr3).toBeNull();
|
|
4241
|
+
});
|