@finos/legend-query-builder 4.1.15 → 4.2.0

Sign up to get free protection for your applications and to get access to all the features.
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
  };