@finos/legend-query-builder 4.1.15 → 4.2.0

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.
Files changed (86) hide show
  1. package/lib/components/filter/QueryBuilderFilterPanel.d.ts +6 -1
  2. package/lib/components/filter/QueryBuilderFilterPanel.d.ts.map +1 -1
  3. package/lib/components/filter/QueryBuilderFilterPanel.js +189 -13
  4. package/lib/components/filter/QueryBuilderFilterPanel.js.map +1 -1
  5. package/lib/index.css +2 -2
  6. package/lib/index.css.map +1 -1
  7. package/lib/package.json +1 -1
  8. package/lib/stores/QueryBuilderStateHashUtils.d.ts +1 -0
  9. package/lib/stores/QueryBuilderStateHashUtils.d.ts.map +1 -1
  10. package/lib/stores/QueryBuilderStateHashUtils.js +1 -0
  11. package/lib/stores/QueryBuilderStateHashUtils.js.map +1 -1
  12. package/lib/stores/filter/QueryBuilderFilterOperator.d.ts +1 -1
  13. package/lib/stores/filter/QueryBuilderFilterOperator.d.ts.map +1 -1
  14. package/lib/stores/filter/QueryBuilderFilterOperator.js.map +1 -1
  15. package/lib/stores/filter/QueryBuilderFilterState.d.ts +16 -5
  16. package/lib/stores/filter/QueryBuilderFilterState.d.ts.map +1 -1
  17. package/lib/stores/filter/QueryBuilderFilterState.js +78 -18
  18. package/lib/stores/filter/QueryBuilderFilterState.js.map +1 -1
  19. package/lib/stores/filter/QueryBuilderFilterStateBuilder.d.ts.map +1 -1
  20. package/lib/stores/filter/QueryBuilderFilterStateBuilder.js +125 -2
  21. package/lib/stores/filter/QueryBuilderFilterStateBuilder.js.map +1 -1
  22. package/lib/stores/filter/QueryBuilderFilterValueSpecificationBuilder.d.ts.map +1 -1
  23. package/lib/stores/filter/QueryBuilderFilterValueSpecificationBuilder.js +62 -3
  24. package/lib/stores/filter/QueryBuilderFilterValueSpecificationBuilder.js.map +1 -1
  25. package/lib/stores/filter/operators/QueryBuilderFilterOperatorValueSpecificationBuilder.d.ts +1 -1
  26. package/lib/stores/filter/operators/QueryBuilderFilterOperatorValueSpecificationBuilder.d.ts.map +1 -1
  27. package/lib/stores/filter/operators/QueryBuilderFilterOperatorValueSpecificationBuilder.js +12 -192
  28. package/lib/stores/filter/operators/QueryBuilderFilterOperatorValueSpecificationBuilder.js.map +1 -1
  29. package/lib/stores/filter/operators/QueryBuilderFilterOperator_Contain.d.ts +2 -2
  30. package/lib/stores/filter/operators/QueryBuilderFilterOperator_Contain.d.ts.map +1 -1
  31. package/lib/stores/filter/operators/QueryBuilderFilterOperator_Contain.js +4 -4
  32. package/lib/stores/filter/operators/QueryBuilderFilterOperator_Contain.js.map +1 -1
  33. package/lib/stores/filter/operators/QueryBuilderFilterOperator_EndWith.d.ts +2 -2
  34. package/lib/stores/filter/operators/QueryBuilderFilterOperator_EndWith.d.ts.map +1 -1
  35. package/lib/stores/filter/operators/QueryBuilderFilterOperator_EndWith.js +4 -4
  36. package/lib/stores/filter/operators/QueryBuilderFilterOperator_EndWith.js.map +1 -1
  37. package/lib/stores/filter/operators/QueryBuilderFilterOperator_Equal.d.ts +2 -2
  38. package/lib/stores/filter/operators/QueryBuilderFilterOperator_Equal.d.ts.map +1 -1
  39. package/lib/stores/filter/operators/QueryBuilderFilterOperator_Equal.js +4 -4
  40. package/lib/stores/filter/operators/QueryBuilderFilterOperator_Equal.js.map +1 -1
  41. package/lib/stores/filter/operators/QueryBuilderFilterOperator_GreaterThan.d.ts +1 -1
  42. package/lib/stores/filter/operators/QueryBuilderFilterOperator_GreaterThan.d.ts.map +1 -1
  43. package/lib/stores/filter/operators/QueryBuilderFilterOperator_GreaterThan.js +2 -2
  44. package/lib/stores/filter/operators/QueryBuilderFilterOperator_GreaterThan.js.map +1 -1
  45. package/lib/stores/filter/operators/QueryBuilderFilterOperator_GreaterThanEqual.d.ts +1 -1
  46. package/lib/stores/filter/operators/QueryBuilderFilterOperator_GreaterThanEqual.d.ts.map +1 -1
  47. package/lib/stores/filter/operators/QueryBuilderFilterOperator_GreaterThanEqual.js +2 -2
  48. package/lib/stores/filter/operators/QueryBuilderFilterOperator_GreaterThanEqual.js.map +1 -1
  49. package/lib/stores/filter/operators/QueryBuilderFilterOperator_In.d.ts +2 -2
  50. package/lib/stores/filter/operators/QueryBuilderFilterOperator_In.d.ts.map +1 -1
  51. package/lib/stores/filter/operators/QueryBuilderFilterOperator_In.js +4 -4
  52. package/lib/stores/filter/operators/QueryBuilderFilterOperator_In.js.map +1 -1
  53. package/lib/stores/filter/operators/QueryBuilderFilterOperator_IsEmpty.d.ts +2 -2
  54. package/lib/stores/filter/operators/QueryBuilderFilterOperator_IsEmpty.d.ts.map +1 -1
  55. package/lib/stores/filter/operators/QueryBuilderFilterOperator_IsEmpty.js +4 -4
  56. package/lib/stores/filter/operators/QueryBuilderFilterOperator_IsEmpty.js.map +1 -1
  57. package/lib/stores/filter/operators/QueryBuilderFilterOperator_LessThan.d.ts +1 -1
  58. package/lib/stores/filter/operators/QueryBuilderFilterOperator_LessThan.d.ts.map +1 -1
  59. package/lib/stores/filter/operators/QueryBuilderFilterOperator_LessThan.js +2 -2
  60. package/lib/stores/filter/operators/QueryBuilderFilterOperator_LessThan.js.map +1 -1
  61. package/lib/stores/filter/operators/QueryBuilderFilterOperator_LessThanEqual.d.ts +1 -1
  62. package/lib/stores/filter/operators/QueryBuilderFilterOperator_LessThanEqual.d.ts.map +1 -1
  63. package/lib/stores/filter/operators/QueryBuilderFilterOperator_LessThanEqual.js +2 -2
  64. package/lib/stores/filter/operators/QueryBuilderFilterOperator_LessThanEqual.js.map +1 -1
  65. package/lib/stores/filter/operators/QueryBuilderFilterOperator_StartWith.d.ts +2 -2
  66. package/lib/stores/filter/operators/QueryBuilderFilterOperator_StartWith.d.ts.map +1 -1
  67. package/lib/stores/filter/operators/QueryBuilderFilterOperator_StartWith.js +4 -4
  68. package/lib/stores/filter/operators/QueryBuilderFilterOperator_StartWith.js.map +1 -1
  69. package/package.json +1 -1
  70. package/src/components/filter/QueryBuilderFilterPanel.tsx +355 -24
  71. package/src/stores/QueryBuilderStateHashUtils.ts +1 -0
  72. package/src/stores/filter/QueryBuilderFilterOperator.ts +1 -0
  73. package/src/stores/filter/QueryBuilderFilterState.ts +115 -27
  74. package/src/stores/filter/QueryBuilderFilterStateBuilder.ts +244 -0
  75. package/src/stores/filter/QueryBuilderFilterValueSpecificationBuilder.ts +94 -1
  76. package/src/stores/filter/operators/QueryBuilderFilterOperatorValueSpecificationBuilder.ts +37 -377
  77. package/src/stores/filter/operators/QueryBuilderFilterOperator_Contain.ts +7 -1
  78. package/src/stores/filter/operators/QueryBuilderFilterOperator_EndWith.ts +7 -1
  79. package/src/stores/filter/operators/QueryBuilderFilterOperator_Equal.ts +7 -1
  80. package/src/stores/filter/operators/QueryBuilderFilterOperator_GreaterThan.ts +2 -0
  81. package/src/stores/filter/operators/QueryBuilderFilterOperator_GreaterThanEqual.ts +2 -0
  82. package/src/stores/filter/operators/QueryBuilderFilterOperator_In.ts +7 -1
  83. package/src/stores/filter/operators/QueryBuilderFilterOperator_IsEmpty.ts +7 -1
  84. package/src/stores/filter/operators/QueryBuilderFilterOperator_LessThan.ts +2 -0
  85. package/src/stores/filter/operators/QueryBuilderFilterOperator_LessThanEqual.ts +2 -0
  86. package/src/stores/filter/operators/QueryBuilderFilterOperator_StartWith.ts +7 -1
@@ -257,47 +257,36 @@ export abstract class QueryBuilderFilterTreeNodeData
257
257
  abstract get hashCode(): string;
258
258
  }
259
259
 
260
- export class QueryBuilderFilterTreeGroupNodeData
260
+ export abstract class QueryBuilderFilterTreeOperationNodeData
261
261
  extends QueryBuilderFilterTreeNodeData
262
262
  implements Hashable
263
263
  {
264
- groupOperation: QUERY_BUILDER_GROUP_OPERATION;
265
264
  childrenIds: string[] = [];
265
+ lambdaParameterName?: string | undefined;
266
266
 
267
- constructor(
268
- parentId: string | undefined,
269
- groupOperation: QUERY_BUILDER_GROUP_OPERATION,
270
- ) {
267
+ constructor(parentId: string | undefined) {
271
268
  super(parentId);
272
269
 
273
270
  makeObservable(this, {
274
- groupOperation: observable,
275
271
  childrenIds: observable,
276
- setGroupOperation: action,
277
272
  addChildNode: action,
278
273
  removeChildNode: action,
279
274
  dragPreviewLabel: computed,
280
275
  });
281
276
 
282
- this.groupOperation = groupOperation;
283
277
  this.isOpen = true;
284
278
  }
285
279
 
286
- get dragPreviewLabel(): string {
287
- return `${this.groupOperation.toUpperCase()} group`;
288
- }
289
-
290
- setGroupOperation(val: QUERY_BUILDER_GROUP_OPERATION): void {
291
- this.groupOperation = val;
292
- }
293
280
  addChildNode(node: QueryBuilderFilterTreeNodeData): void {
294
281
  addUniqueEntry(this.childrenIds, node.id);
295
282
  node.setParentId(this.id);
296
283
  }
284
+
297
285
  removeChildNode(node: QueryBuilderFilterTreeNodeData): void {
298
286
  deleteEntry(this.childrenIds, node.id);
299
287
  node.setParentId(undefined);
300
288
  }
289
+
301
290
  addChildNodeAt(node: QueryBuilderFilterTreeNodeData, idx: number): void {
302
291
  if (!this.childrenIds.find((childId) => childId === node.id)) {
303
292
  idx = Math.max(0, Math.min(idx, this.childrenIds.length - 1));
@@ -305,6 +294,36 @@ export class QueryBuilderFilterTreeGroupNodeData
305
294
  node.setParentId(this.id);
306
295
  }
307
296
  }
297
+ }
298
+
299
+ export class QueryBuilderFilterTreeGroupNodeData
300
+ extends QueryBuilderFilterTreeOperationNodeData
301
+ implements Hashable
302
+ {
303
+ groupOperation: QUERY_BUILDER_GROUP_OPERATION;
304
+
305
+ constructor(
306
+ parentId: string | undefined,
307
+ groupOperation: QUERY_BUILDER_GROUP_OPERATION,
308
+ ) {
309
+ super(parentId);
310
+
311
+ makeObservable(this, {
312
+ groupOperation: observable,
313
+ setGroupOperation: action,
314
+ });
315
+
316
+ this.groupOperation = groupOperation;
317
+ this.isOpen = true;
318
+ }
319
+
320
+ get dragPreviewLabel(): string {
321
+ return `${this.groupOperation.toUpperCase()} group`;
322
+ }
323
+
324
+ setGroupOperation(val: QUERY_BUILDER_GROUP_OPERATION): void {
325
+ this.groupOperation = val;
326
+ }
308
327
 
309
328
  get hashCode(): string {
310
329
  return hashArray([
@@ -312,6 +331,43 @@ export class QueryBuilderFilterTreeGroupNodeData
312
331
  this.parentId ?? '',
313
332
  hashArray(this.childrenIds),
314
333
  this.groupOperation,
334
+ this.lambdaParameterName ?? '',
335
+ ]);
336
+ }
337
+ }
338
+
339
+ export class QueryBuilderFilterTreeExistsNodeData
340
+ extends QueryBuilderFilterTreeOperationNodeData
341
+ implements Hashable
342
+ {
343
+ propertyExpression!: AbstractPropertyExpression;
344
+
345
+ constructor(parentId: string | undefined) {
346
+ super(parentId);
347
+
348
+ makeObservable(this, {
349
+ propertyExpression: observable,
350
+ setPropertyExpression: action,
351
+ });
352
+
353
+ this.isOpen = true;
354
+ }
355
+
356
+ get dragPreviewLabel(): string {
357
+ return `exists`;
358
+ }
359
+
360
+ setPropertyExpression(val: AbstractPropertyExpression): void {
361
+ this.propertyExpression = val;
362
+ }
363
+
364
+ get hashCode(): string {
365
+ return hashArray([
366
+ QUERY_BUILDER_STATE_HASH_STRUCTURE.FILTER_TREE_EXISTS_NODE_DATA,
367
+ this.parentId ?? '',
368
+ hashArray(this.childrenIds),
369
+ this.propertyExpression,
370
+ this.lambdaParameterName ?? '',
315
371
  ]);
316
372
  }
317
373
  }
@@ -449,11 +505,11 @@ export class QueryBuilderFilterState
449
505
 
450
506
  private getParentNode(
451
507
  node: QueryBuilderFilterTreeNodeData,
452
- ): QueryBuilderFilterTreeGroupNodeData | undefined {
508
+ ): QueryBuilderFilterTreeOperationNodeData | undefined {
453
509
  return node.parentId
454
510
  ? guaranteeType(
455
511
  this.nodes.get(node.parentId),
456
- QueryBuilderFilterTreeGroupNodeData,
512
+ QueryBuilderFilterTreeOperationNodeData,
457
513
  )
458
514
  : undefined;
459
515
  }
@@ -465,7 +521,8 @@ export class QueryBuilderFilterState
465
521
  rootNode.addChildNode(node);
466
522
  } else if (
467
523
  rootNode instanceof QueryBuilderFilterTreeConditionNodeData ||
468
- rootNode instanceof QueryBuilderFilterTreeBlankConditionNodeData
524
+ rootNode instanceof QueryBuilderFilterTreeBlankConditionNodeData ||
525
+ rootNode instanceof QueryBuilderFilterTreeExistsNodeData
469
526
  ) {
470
527
  // if the root node is condition node, form a group between the root node and the new node and nominate the group node as the new root
471
528
  const groupNode = new QueryBuilderFilterTreeGroupNodeData(
@@ -489,6 +546,31 @@ export class QueryBuilderFilterState
489
546
  if (fromNode instanceof QueryBuilderFilterTreeGroupNodeData) {
490
547
  this.nodes.set(node.id, node);
491
548
  fromNode.addChildNode(node);
549
+ } else if (fromNode instanceof QueryBuilderFilterTreeExistsNodeData) {
550
+ // Here we check if there are any child nodes for exists node. The rationale
551
+ // behind doing this check is if there are no childs we can just add a child
552
+ // node to this otherwise we need to add a group condition for the existing
553
+ // child node and the node we are trying to add.
554
+ if (!fromNode.childrenIds.length) {
555
+ this.nodes.set(node.id, node);
556
+ fromNode.addChildNode(node);
557
+ } else {
558
+ this.nodes.set(node.id, node);
559
+ const groupNode = new QueryBuilderFilterTreeGroupNodeData(
560
+ undefined,
561
+ QUERY_BUILDER_GROUP_OPERATION.AND,
562
+ );
563
+ groupNode.addChildNode(
564
+ guaranteeNonNullable(
565
+ this.nodes.get(guaranteeNonNullable(fromNode.childrenIds[0])),
566
+ ),
567
+ );
568
+ groupNode.addChildNode(node);
569
+ groupNode.lambdaParameterName = fromNode.lambdaParameterName;
570
+ this.nodes.set(groupNode.id, groupNode);
571
+ fromNode.childrenIds = [];
572
+ fromNode.addChildNode(groupNode);
573
+ }
492
574
  } else if (
493
575
  fromNode instanceof QueryBuilderFilterTreeConditionNodeData ||
494
576
  fromNode instanceof QueryBuilderFilterTreeBlankConditionNodeData
@@ -563,6 +645,7 @@ export class QueryBuilderFilterState
563
645
  this.nodes.set(newGroupNode.id, newGroupNode);
564
646
  newGroupNode.addChildNode(fromNode);
565
647
  newGroupNode.addChildNode(newNode);
648
+ newGroupNode.lambdaParameterName = fromNodeParent.lambdaParameterName;
566
649
  fromNodeParent.addChildNodeAt(newGroupNode, fromNodeIdx);
567
650
  } else {
568
651
  this.addRootNode(newNode);
@@ -573,7 +656,7 @@ export class QueryBuilderFilterState
573
656
  private removeNode(node: QueryBuilderFilterTreeNodeData): void {
574
657
  this.nodes.delete(node.id);
575
658
  // remove relationship with children nodes
576
- if (node instanceof QueryBuilderFilterTreeGroupNodeData) {
659
+ if (node instanceof QueryBuilderFilterTreeOperationNodeData) {
577
660
  // NOTE: we are deleting child node, i.e. modifying `childrenIds` as we iterate
578
661
  [...node.childrenIds].forEach((childId) =>
579
662
  node.removeChildNode(this.getNode(childId)),
@@ -644,9 +727,13 @@ export class QueryBuilderFilterState
644
727
  // squash parent node after the current node is deleted
645
728
  if (parentNode) {
646
729
  parentNode.removeChildNode(node);
647
- let currentParentNode: QueryBuilderFilterTreeGroupNodeData | undefined =
648
- parentNode;
649
- while (currentParentNode) {
730
+ let currentParentNode:
731
+ | QueryBuilderFilterTreeOperationNodeData
732
+ | undefined = parentNode;
733
+ while (
734
+ currentParentNode &&
735
+ currentParentNode instanceof QueryBuilderFilterTreeGroupNodeData
736
+ ) {
650
737
  if (currentParentNode.childrenIds.length >= 2) {
651
738
  break;
652
739
  }
@@ -724,11 +811,12 @@ export class QueryBuilderFilterState
724
811
  if (!node.parentId || !this.nodes.has(node.parentId)) {
725
812
  return false;
726
813
  }
727
- const parentGroupNode = guaranteeType(
728
- this.nodes.get(node.parentId),
729
- QueryBuilderFilterTreeGroupNodeData,
814
+ const parentGroupNode = this.nodes.get(node.parentId);
815
+
816
+ return (
817
+ parentGroupNode instanceof QueryBuilderFilterTreeGroupNodeData &&
818
+ parentGroupNode.groupOperation === node.groupOperation
730
819
  );
731
- return parentGroupNode.groupOperation === node.groupOperation;
732
820
  });
733
821
  // Squash these unnecessary group nodes
734
822
  let nodesToProcess = getUnnecessaryNodes();
@@ -16,9 +16,12 @@
16
16
 
17
17
  import {
18
18
  AbstractPropertyExpression,
19
+ extractElementNameFromPath,
20
+ LambdaFunction,
19
21
  LambdaFunctionInstanceValue,
20
22
  matchFunctionName,
21
23
  SimpleFunctionExpression,
24
+ type ValueSpecification,
22
25
  VariableExpression,
23
26
  } from '@finos/legend-graph';
24
27
  import {
@@ -37,8 +40,35 @@ import {
37
40
  type QueryBuilderFilterState,
38
41
  QueryBuilderFilterTreeConditionNodeData,
39
42
  QueryBuilderFilterTreeGroupNodeData,
43
+ QueryBuilderFilterTreeExistsNodeData,
44
+ QueryBuilderFilterTreeOperationNodeData,
40
45
  } from './QueryBuilderFilterState.js';
41
46
 
47
+ const getPropertyExpressionChainVariable = (
48
+ propertyExpression: AbstractPropertyExpression,
49
+ ): VariableExpression => {
50
+ let currentExpression: ValueSpecification = propertyExpression;
51
+ while (currentExpression instanceof AbstractPropertyExpression) {
52
+ currentExpression = guaranteeNonNullable(
53
+ currentExpression.parametersValues[0],
54
+ );
55
+ // Take care of chains of subtype (a pattern that is not useful, but we want to support and rectify)
56
+ // $x.employees->subType(@Person)->subType(@Staff)
57
+ while (
58
+ currentExpression instanceof SimpleFunctionExpression &&
59
+ matchFunctionName(
60
+ currentExpression.functionName,
61
+ QUERY_BUILDER_SUPPORTED_FUNCTIONS.SUBTYPE,
62
+ )
63
+ ) {
64
+ currentExpression = guaranteeNonNullable(
65
+ currentExpression.parametersValues[0],
66
+ );
67
+ }
68
+ }
69
+ return guaranteeType(currentExpression, VariableExpression);
70
+ };
71
+
42
72
  const processFilterTree = (
43
73
  expression: SimpleFunctionExpression,
44
74
  filterState: QueryBuilderFilterState,
@@ -58,6 +88,12 @@ const processFilterTree = (
58
88
  toGroupOperation(expression.functionName),
59
89
  );
60
90
  filterState.nodes.set(groupNode.id, groupNode);
91
+ if (parentNode) {
92
+ groupNode.lambdaParameterName = guaranteeType(
93
+ parentNode,
94
+ QueryBuilderFilterTreeOperationNodeData,
95
+ ).lambdaParameterName;
96
+ }
61
97
  expression.parametersValues.forEach((filterExpression) =>
62
98
  processFilterTree(
63
99
  guaranteeType(
@@ -70,6 +106,200 @@ const processFilterTree = (
70
106
  ),
71
107
  );
72
108
  filterState.addNodeFromNode(groupNode, parentNode);
109
+ } else if (
110
+ matchFunctionName(expression.functionName, [
111
+ QUERY_BUILDER_SUPPORTED_FUNCTIONS.EXISTS,
112
+ ])
113
+ ) {
114
+ const propertyExpression = guaranteeType(
115
+ expression.parametersValues[0],
116
+ AbstractPropertyExpression,
117
+ );
118
+ if (
119
+ propertyExpression.func.value.multiplicity.upperBound === undefined ||
120
+ propertyExpression.func.value.multiplicity.upperBound > 1
121
+ ) {
122
+ const existsNode = new QueryBuilderFilterTreeExistsNodeData(
123
+ parentFilterNodeId,
124
+ );
125
+ const lambdaFunctionInstance = guaranteeType(
126
+ expression.parametersValues[1],
127
+ LambdaFunctionInstanceValue,
128
+ `Can't process filter expression: only supports exists with second paramter as LambdaFunctionInstanceValue`,
129
+ );
130
+ const lambdaFunction = guaranteeType(
131
+ lambdaFunctionInstance.values[0],
132
+ LambdaFunction,
133
+ );
134
+ const filterExpression = guaranteeType(
135
+ lambdaFunction.expressionSequence[0],
136
+ SimpleFunctionExpression,
137
+ );
138
+ existsNode.setPropertyExpression(propertyExpression);
139
+ existsNode.lambdaParameterName =
140
+ lambdaFunction.functionType.parameters[0]?.name;
141
+ filterState.nodes.set(existsNode.id, existsNode);
142
+ processFilterTree(filterExpression, filterState, existsNode.id);
143
+ filterState.addNodeFromNode(existsNode, parentNode);
144
+ } else {
145
+ // Build property chain if we have exists filter() and the multiplicity of the property
146
+ // is of multiplicity less than or equal to one. This will auto-fix the exists into the
147
+ // desired filter expression
148
+
149
+ // 1. Decompose the exists() lambda chain into property expression chains
150
+ const existsLambdaParameterNames: string[] = [];
151
+
152
+ const existsLambdaExpressions: AbstractPropertyExpression[] = [];
153
+ let mainFilterExpression: SimpleFunctionExpression = expression;
154
+ while (
155
+ matchFunctionName(
156
+ mainFilterExpression.functionName,
157
+ QUERY_BUILDER_SUPPORTED_FUNCTIONS.EXISTS,
158
+ )
159
+ ) {
160
+ const existsLambda = guaranteeNonNullable(
161
+ guaranteeType(
162
+ mainFilterExpression.parametersValues[1],
163
+ LambdaFunctionInstanceValue,
164
+ ).values[0],
165
+ `Can't process exists() expression: exists() lambda is missing`,
166
+ );
167
+ assertTrue(
168
+ existsLambda.expressionSequence.length === 1,
169
+ `Can't process exists() expression: exists() lambda body should hold an expression`,
170
+ );
171
+ mainFilterExpression = guaranteeType(
172
+ existsLambda.expressionSequence[0],
173
+ SimpleFunctionExpression,
174
+ `Can't process exists() expression: exists() lambda body should hold an expression`,
175
+ );
176
+
177
+ // record the lambda parameter name
178
+ assertTrue(
179
+ existsLambda.functionType.parameters.length === 1,
180
+ `Can't process exists() expression: exists() lambda should have 1 parameter`,
181
+ );
182
+ existsLambdaParameterNames.push(
183
+ guaranteeType(
184
+ existsLambda.functionType.parameters[0],
185
+ VariableExpression,
186
+ `Can't process exists() expression: exists() lambda should have 1 parameter`,
187
+ ).name,
188
+ );
189
+
190
+ // record the lambda property expression
191
+ if (
192
+ mainFilterExpression.parametersValues[0] instanceof
193
+ AbstractPropertyExpression &&
194
+ !(
195
+ mainFilterExpression.parametersValues[0].func.value.multiplicity
196
+ .upperBound === undefined ||
197
+ mainFilterExpression.parametersValues[0].func.value.multiplicity
198
+ .upperBound > 1
199
+ )
200
+ ) {
201
+ existsLambdaExpressions.push(
202
+ mainFilterExpression.parametersValues[0],
203
+ );
204
+ } else {
205
+ break;
206
+ }
207
+ }
208
+
209
+ // 2. Build the property expression
210
+ const initialPropertyExpression = guaranteeType(
211
+ expression.parametersValues[0],
212
+ AbstractPropertyExpression,
213
+ );
214
+ let flattenedPropertyExpressionChain = new AbstractPropertyExpression('');
215
+ flattenedPropertyExpressionChain.func = initialPropertyExpression.func;
216
+ flattenedPropertyExpressionChain.parametersValues = [
217
+ ...initialPropertyExpression.parametersValues,
218
+ ];
219
+ for (const exp of existsLambdaExpressions) {
220
+ // when rebuilding the property expression chain, disregard the initial variable that starts the chain
221
+ const expressions: (
222
+ | AbstractPropertyExpression
223
+ | SimpleFunctionExpression
224
+ )[] = [];
225
+ let currentExpression: ValueSpecification = exp;
226
+ while (
227
+ currentExpression instanceof AbstractPropertyExpression ||
228
+ (currentExpression instanceof SimpleFunctionExpression &&
229
+ matchFunctionName(
230
+ currentExpression.functionName,
231
+ QUERY_BUILDER_SUPPORTED_FUNCTIONS.SUBTYPE,
232
+ ))
233
+ ) {
234
+ if (currentExpression instanceof SimpleFunctionExpression) {
235
+ const functionExpression = new SimpleFunctionExpression(
236
+ extractElementNameFromPath(
237
+ QUERY_BUILDER_SUPPORTED_FUNCTIONS.SUBTYPE,
238
+ ),
239
+ );
240
+ functionExpression.parametersValues.unshift(
241
+ guaranteeNonNullable(currentExpression.parametersValues[1]),
242
+ );
243
+ expressions.push(functionExpression);
244
+ } else if (currentExpression instanceof AbstractPropertyExpression) {
245
+ const propertyExp = new AbstractPropertyExpression('');
246
+ propertyExp.func = currentExpression.func;
247
+ // NOTE: we must retain the rest of the parameters as those are derived property parameters
248
+ propertyExp.parametersValues =
249
+ currentExpression.parametersValues.length > 1
250
+ ? currentExpression.parametersValues.slice(1)
251
+ : [];
252
+ expressions.push(propertyExp);
253
+ }
254
+ currentExpression = guaranteeNonNullable(
255
+ currentExpression.parametersValues[0],
256
+ );
257
+ }
258
+ assertTrue(
259
+ expressions.length > 0,
260
+ `Can't process exists() expression: exists() usage with non-chain property expression is not supported`,
261
+ );
262
+ for (let i = 0; i < expressions.length - 1; ++i) {
263
+ (
264
+ expressions[i] as
265
+ | AbstractPropertyExpression
266
+ | SimpleFunctionExpression
267
+ ).parametersValues.unshift(
268
+ expressions[i + 1] as
269
+ | AbstractPropertyExpression
270
+ | SimpleFunctionExpression,
271
+ );
272
+ }
273
+ (
274
+ expressions[expressions.length - 1] as
275
+ | AbstractPropertyExpression
276
+ | SimpleFunctionExpression
277
+ ).parametersValues.unshift(flattenedPropertyExpressionChain);
278
+ flattenedPropertyExpressionChain = guaranteeType(
279
+ expressions[0],
280
+ AbstractPropertyExpression,
281
+ `Can't process exists() expression: can't flatten to a property expression`,
282
+ );
283
+ }
284
+ mainFilterExpression.parametersValues =
285
+ mainFilterExpression.parametersValues.map((p) => {
286
+ if (
287
+ p instanceof SimpleFunctionExpression &&
288
+ p.parametersValues[0] instanceof AbstractPropertyExpression
289
+ ) {
290
+ p.parametersValues[0].parametersValues[0] =
291
+ flattenedPropertyExpressionChain;
292
+ } else if (p instanceof AbstractPropertyExpression) {
293
+ p.parametersValues[0] = guaranteeNonNullable(
294
+ flattenedPropertyExpressionChain.parametersValues[0],
295
+ );
296
+ } else if (p instanceof VariableExpression) {
297
+ return flattenedPropertyExpressionChain;
298
+ }
299
+ return p;
300
+ });
301
+ processFilterTree(mainFilterExpression, filterState, parentFilterNodeId);
302
+ }
73
303
  } else {
74
304
  const propertyExpression = expression.parametersValues[0];
75
305
  if (propertyExpression instanceof AbstractPropertyExpression) {
@@ -89,6 +319,20 @@ const processFilterTree = (
89
319
  operator.buildFilterConditionState(filterState, expression),
90
320
  );
91
321
  if (filterConditionState) {
322
+ const variableName = getPropertyExpressionChainVariable(
323
+ filterConditionState.propertyExpressionState.propertyExpression,
324
+ ).name;
325
+ const parentLambdaVariableName =
326
+ parentNode instanceof QueryBuilderFilterTreeOperationNodeData &&
327
+ parentNode.lambdaParameterName
328
+ ? parentNode.lambdaParameterName
329
+ : filterState.lambdaParameterName;
330
+ assertTrue(
331
+ parentLambdaVariableName === variableName,
332
+ `Can't process ${extractElementNameFromPath(
333
+ filterConditionState.operator.getLabel(filterConditionState),
334
+ )}() expression: expects variable used in lambda body '${variableName}' to match lambda parameter '${parentLambdaVariableName}'`,
335
+ );
92
336
  filterState.addNodeFromNode(
93
337
  new QueryBuilderFilterTreeConditionNodeData(
94
338
  undefined,
@@ -14,7 +14,11 @@
14
14
  * limitations under the License.
15
15
  */
16
16
 
17
- import { isNonNullable, guaranteeNonNullable } from '@finos/legend-shared';
17
+ import {
18
+ isNonNullable,
19
+ guaranteeNonNullable,
20
+ guaranteeType,
21
+ } from '@finos/legend-shared';
18
22
  import {
19
23
  type ValueSpecification,
20
24
  type LambdaFunction,
@@ -28,16 +32,26 @@ import {
28
32
  QueryBuilderFilterTreeConditionNodeData,
29
33
  QueryBuilderFilterTreeGroupNodeData,
30
34
  type QueryBuilderFilterTreeNodeData,
35
+ QueryBuilderFilterTreeExistsNodeData,
36
+ QueryBuilderFilterTreeOperationNodeData,
31
37
  } from './QueryBuilderFilterState.js';
32
38
  import { QUERY_BUILDER_SUPPORTED_FUNCTIONS } from '../../graph/QueryBuilderMetaModelConst.js';
39
+ import { DEFAULT_LAMBDA_VARIABLE_NAME } from '../QueryBuilderConfig.js';
33
40
 
34
41
  const buildFilterConditionExpression = (
35
42
  filterState: QueryBuilderFilterState,
36
43
  node: QueryBuilderFilterTreeNodeData,
37
44
  ): ValueSpecification | undefined => {
38
45
  if (node instanceof QueryBuilderFilterTreeConditionNodeData) {
46
+ const parentNode = node.parentId
47
+ ? guaranteeType(
48
+ filterState.nodes.get(node.parentId),
49
+ QueryBuilderFilterTreeOperationNodeData,
50
+ )
51
+ : undefined;
39
52
  return node.condition.operator.buildFilterConditionExpression(
40
53
  node.condition,
54
+ parentNode?.lambdaParameterName,
41
55
  );
42
56
  } else if (node instanceof QueryBuilderFilterTreeGroupNodeData) {
43
57
  const func = new SimpleFunctionExpression(
@@ -75,6 +89,85 @@ const buildFilterConditionExpression = (
75
89
  func.parametersValues = clauses;
76
90
  }
77
91
  return func.parametersValues.length ? func : undefined;
92
+ } else if (node instanceof QueryBuilderFilterTreeExistsNodeData) {
93
+ const func = new SimpleFunctionExpression(
94
+ extractElementNameFromPath(QUERY_BUILDER_SUPPORTED_FUNCTIONS.EXISTS),
95
+ );
96
+ let parentNode = node.parentId
97
+ ? guaranteeType(
98
+ filterState.nodes.get(guaranteeNonNullable(node.parentId)),
99
+ QueryBuilderFilterTreeOperationNodeData,
100
+ )
101
+ : undefined;
102
+ while (
103
+ parentNode &&
104
+ !(parentNode instanceof QueryBuilderFilterTreeGroupNodeData)
105
+ ) {
106
+ parentNode = parentNode.parentId
107
+ ? guaranteeType(
108
+ filterState.nodes.get(guaranteeNonNullable(parentNode.parentId)),
109
+ QueryBuilderFilterTreeOperationNodeData,
110
+ )
111
+ : undefined;
112
+ }
113
+ const propertyExpression = guaranteeNonNullable(node.propertyExpression);
114
+ const clauses = node.childrenIds
115
+ .map((e) => filterState.nodes.get(e))
116
+ .filter(isNonNullable)
117
+ .map((e) => buildFilterConditionExpression(filterState, e))
118
+ .filter(isNonNullable);
119
+ /**
120
+ * NOTE: Due to a limitation (or perhaps design decision) in the engine, group operations
121
+ * like and/or do not take more than 2 parameters, as such, if we have more than 2, we need
122
+ * to create a chain of this operation to accomondate.
123
+ *
124
+ * This means that in the read direction, we might need to flatten the chains down to group with
125
+ * multiple clauses. This means user's intended grouping will not be kept.
126
+ */
127
+ let parametersValues;
128
+ if (clauses.length > 2) {
129
+ const firstClause = clauses[0] as ValueSpecification;
130
+ let currentClause: ValueSpecification = clauses[
131
+ clauses.length - 1
132
+ ] as ValueSpecification;
133
+ for (let i = clauses.length - 2; i > 0; --i) {
134
+ const clause1 = clauses[i] as ValueSpecification;
135
+ const clause2 = currentClause;
136
+ const groupClause = new SimpleFunctionExpression(
137
+ guaranteeNonNullable(parentNode).groupOperation,
138
+ );
139
+ groupClause.parametersValues = [clause1, clause2];
140
+ currentClause = groupClause;
141
+ }
142
+ parametersValues = [firstClause, currentClause];
143
+ } else if (clauses.length === 1) {
144
+ const lambdaFunctionInstanceValue =
145
+ buildGenericLambdaFunctionInstanceValue(
146
+ node.lambdaParameterName ?? DEFAULT_LAMBDA_VARIABLE_NAME,
147
+ clauses,
148
+ filterState.queryBuilderState.graphManagerState.graph,
149
+ );
150
+ func.parametersValues = [propertyExpression, lambdaFunctionInstanceValue];
151
+ return func;
152
+ } else {
153
+ parametersValues = clauses;
154
+ }
155
+ if (!parametersValues.length) {
156
+ return undefined;
157
+ }
158
+ const simp = new SimpleFunctionExpression(
159
+ extractElementNameFromPath(
160
+ fromGroupOperation(guaranteeNonNullable(parentNode).groupOperation),
161
+ ),
162
+ );
163
+ simp.parametersValues = parametersValues;
164
+ const lambdaFunctionInstanceValue = buildGenericLambdaFunctionInstanceValue(
165
+ node.lambdaParameterName ?? DEFAULT_LAMBDA_VARIABLE_NAME,
166
+ [simp],
167
+ filterState.queryBuilderState.graphManagerState.graph,
168
+ );
169
+ func.parametersValues = [propertyExpression, lambdaFunctionInstanceValue];
170
+ return func;
78
171
  }
79
172
  return undefined;
80
173
  };