@finos/legend-query-builder 4.1.14 → 4.2.0
Sign up to get free protection for your applications and to get access to all the features.
- package/lib/components/filter/QueryBuilderFilterPanel.d.ts +6 -1
- package/lib/components/filter/QueryBuilderFilterPanel.d.ts.map +1 -1
- package/lib/components/filter/QueryBuilderFilterPanel.js +189 -13
- package/lib/components/filter/QueryBuilderFilterPanel.js.map +1 -1
- package/lib/index.css +17 -1
- package/lib/index.css.map +1 -1
- package/lib/package.json +6 -6
- package/lib/stores/QueryBuilderStateHashUtils.d.ts +1 -0
- package/lib/stores/QueryBuilderStateHashUtils.d.ts.map +1 -1
- package/lib/stores/QueryBuilderStateHashUtils.js +1 -0
- package/lib/stores/QueryBuilderStateHashUtils.js.map +1 -1
- package/lib/stores/filter/QueryBuilderFilterOperator.d.ts +1 -1
- package/lib/stores/filter/QueryBuilderFilterOperator.d.ts.map +1 -1
- package/lib/stores/filter/QueryBuilderFilterOperator.js.map +1 -1
- package/lib/stores/filter/QueryBuilderFilterState.d.ts +16 -5
- package/lib/stores/filter/QueryBuilderFilterState.d.ts.map +1 -1
- package/lib/stores/filter/QueryBuilderFilterState.js +78 -18
- package/lib/stores/filter/QueryBuilderFilterState.js.map +1 -1
- package/lib/stores/filter/QueryBuilderFilterStateBuilder.d.ts.map +1 -1
- package/lib/stores/filter/QueryBuilderFilterStateBuilder.js +125 -2
- package/lib/stores/filter/QueryBuilderFilterStateBuilder.js.map +1 -1
- package/lib/stores/filter/QueryBuilderFilterValueSpecificationBuilder.d.ts.map +1 -1
- package/lib/stores/filter/QueryBuilderFilterValueSpecificationBuilder.js +62 -3
- package/lib/stores/filter/QueryBuilderFilterValueSpecificationBuilder.js.map +1 -1
- package/lib/stores/filter/operators/QueryBuilderFilterOperatorValueSpecificationBuilder.d.ts +1 -1
- package/lib/stores/filter/operators/QueryBuilderFilterOperatorValueSpecificationBuilder.d.ts.map +1 -1
- package/lib/stores/filter/operators/QueryBuilderFilterOperatorValueSpecificationBuilder.js +12 -192
- package/lib/stores/filter/operators/QueryBuilderFilterOperatorValueSpecificationBuilder.js.map +1 -1
- package/lib/stores/filter/operators/QueryBuilderFilterOperator_Contain.d.ts +2 -2
- package/lib/stores/filter/operators/QueryBuilderFilterOperator_Contain.d.ts.map +1 -1
- package/lib/stores/filter/operators/QueryBuilderFilterOperator_Contain.js +4 -4
- package/lib/stores/filter/operators/QueryBuilderFilterOperator_Contain.js.map +1 -1
- package/lib/stores/filter/operators/QueryBuilderFilterOperator_EndWith.d.ts +2 -2
- package/lib/stores/filter/operators/QueryBuilderFilterOperator_EndWith.d.ts.map +1 -1
- package/lib/stores/filter/operators/QueryBuilderFilterOperator_EndWith.js +4 -4
- package/lib/stores/filter/operators/QueryBuilderFilterOperator_EndWith.js.map +1 -1
- package/lib/stores/filter/operators/QueryBuilderFilterOperator_Equal.d.ts +2 -2
- package/lib/stores/filter/operators/QueryBuilderFilterOperator_Equal.d.ts.map +1 -1
- package/lib/stores/filter/operators/QueryBuilderFilterOperator_Equal.js +4 -4
- package/lib/stores/filter/operators/QueryBuilderFilterOperator_Equal.js.map +1 -1
- package/lib/stores/filter/operators/QueryBuilderFilterOperator_GreaterThan.d.ts +1 -1
- package/lib/stores/filter/operators/QueryBuilderFilterOperator_GreaterThan.d.ts.map +1 -1
- package/lib/stores/filter/operators/QueryBuilderFilterOperator_GreaterThan.js +2 -2
- package/lib/stores/filter/operators/QueryBuilderFilterOperator_GreaterThan.js.map +1 -1
- package/lib/stores/filter/operators/QueryBuilderFilterOperator_GreaterThanEqual.d.ts +1 -1
- package/lib/stores/filter/operators/QueryBuilderFilterOperator_GreaterThanEqual.d.ts.map +1 -1
- package/lib/stores/filter/operators/QueryBuilderFilterOperator_GreaterThanEqual.js +2 -2
- package/lib/stores/filter/operators/QueryBuilderFilterOperator_GreaterThanEqual.js.map +1 -1
- package/lib/stores/filter/operators/QueryBuilderFilterOperator_In.d.ts +2 -2
- package/lib/stores/filter/operators/QueryBuilderFilterOperator_In.d.ts.map +1 -1
- package/lib/stores/filter/operators/QueryBuilderFilterOperator_In.js +4 -4
- package/lib/stores/filter/operators/QueryBuilderFilterOperator_In.js.map +1 -1
- package/lib/stores/filter/operators/QueryBuilderFilterOperator_IsEmpty.d.ts +2 -2
- package/lib/stores/filter/operators/QueryBuilderFilterOperator_IsEmpty.d.ts.map +1 -1
- package/lib/stores/filter/operators/QueryBuilderFilterOperator_IsEmpty.js +4 -4
- package/lib/stores/filter/operators/QueryBuilderFilterOperator_IsEmpty.js.map +1 -1
- package/lib/stores/filter/operators/QueryBuilderFilterOperator_LessThan.d.ts +1 -1
- package/lib/stores/filter/operators/QueryBuilderFilterOperator_LessThan.d.ts.map +1 -1
- package/lib/stores/filter/operators/QueryBuilderFilterOperator_LessThan.js +2 -2
- package/lib/stores/filter/operators/QueryBuilderFilterOperator_LessThan.js.map +1 -1
- package/lib/stores/filter/operators/QueryBuilderFilterOperator_LessThanEqual.d.ts +1 -1
- package/lib/stores/filter/operators/QueryBuilderFilterOperator_LessThanEqual.d.ts.map +1 -1
- package/lib/stores/filter/operators/QueryBuilderFilterOperator_LessThanEqual.js +2 -2
- package/lib/stores/filter/operators/QueryBuilderFilterOperator_LessThanEqual.js.map +1 -1
- package/lib/stores/filter/operators/QueryBuilderFilterOperator_StartWith.d.ts +2 -2
- package/lib/stores/filter/operators/QueryBuilderFilterOperator_StartWith.d.ts.map +1 -1
- package/lib/stores/filter/operators/QueryBuilderFilterOperator_StartWith.js +4 -4
- package/lib/stores/filter/operators/QueryBuilderFilterOperator_StartWith.js.map +1 -1
- package/package.json +14 -14
- package/src/components/filter/QueryBuilderFilterPanel.tsx +355 -24
- package/src/stores/QueryBuilderStateHashUtils.ts +1 -0
- package/src/stores/filter/QueryBuilderFilterOperator.ts +1 -0
- package/src/stores/filter/QueryBuilderFilterState.ts +115 -27
- package/src/stores/filter/QueryBuilderFilterStateBuilder.ts +244 -0
- package/src/stores/filter/QueryBuilderFilterValueSpecificationBuilder.ts +94 -1
- package/src/stores/filter/operators/QueryBuilderFilterOperatorValueSpecificationBuilder.ts +37 -377
- package/src/stores/filter/operators/QueryBuilderFilterOperator_Contain.ts +7 -1
- package/src/stores/filter/operators/QueryBuilderFilterOperator_EndWith.ts +7 -1
- package/src/stores/filter/operators/QueryBuilderFilterOperator_Equal.ts +7 -1
- package/src/stores/filter/operators/QueryBuilderFilterOperator_GreaterThan.ts +2 -0
- package/src/stores/filter/operators/QueryBuilderFilterOperator_GreaterThanEqual.ts +2 -0
- package/src/stores/filter/operators/QueryBuilderFilterOperator_In.ts +7 -1
- package/src/stores/filter/operators/QueryBuilderFilterOperator_IsEmpty.ts +7 -1
- package/src/stores/filter/operators/QueryBuilderFilterOperator_LessThan.ts +2 -0
- package/src/stores/filter/operators/QueryBuilderFilterOperator_LessThanEqual.ts +2 -0
- 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
|
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
|
-
):
|
508
|
+
): QueryBuilderFilterTreeOperationNodeData | undefined {
|
453
509
|
return node.parentId
|
454
510
|
? guaranteeType(
|
455
511
|
this.nodes.get(node.parentId),
|
456
|
-
|
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
|
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:
|
648
|
-
|
649
|
-
|
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 =
|
728
|
-
|
729
|
-
|
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 {
|
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
|
};
|