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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flowquery",
3
- "version": "1.0.41",
3
+ "version": "1.0.42",
4
4
  "description": "A declarative query language for data processing pipelines.",
5
5
  "main": "dist/index.node.js",
6
6
  "types": "dist/index.node.d.ts",
@@ -16,7 +16,11 @@ class NodeReference extends Node {
16
16
  return this._reference;
17
17
  }
18
18
  public async next(): Promise<void> {
19
- this.setValue(this._reference!.value()!);
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
+ });