@finos/legend-query-builder 4.18.2 → 4.18.4

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 (113) hide show
  1. package/lib/components/QueryBuilder.d.ts.map +1 -1
  2. package/lib/components/QueryBuilder.js +0 -1
  3. package/lib/components/QueryBuilder.js.map +1 -1
  4. package/lib/components/fetch-structure/QueryBuilderTDSPanel.d.ts.map +1 -1
  5. package/lib/components/fetch-structure/QueryBuilderTDSPanel.js +5 -3
  6. package/lib/components/fetch-structure/QueryBuilderTDSPanel.js.map +1 -1
  7. package/lib/components/filter/QueryBuilderFilterPanel.d.ts.map +1 -1
  8. package/lib/components/filter/QueryBuilderFilterPanel.js +74 -36
  9. package/lib/components/filter/QueryBuilderFilterPanel.js.map +1 -1
  10. package/lib/components/result/tds/QueryBuilderTDSResultShared.d.ts.map +1 -1
  11. package/lib/components/result/tds/QueryBuilderTDSResultShared.js +5 -3
  12. package/lib/components/result/tds/QueryBuilderTDSResultShared.js.map +1 -1
  13. package/lib/data-access-overview.css +1 -1
  14. package/lib/index.css +1 -1
  15. package/lib/index.d.ts +2 -1
  16. package/lib/index.d.ts.map +1 -1
  17. package/lib/index.js +2 -1
  18. package/lib/index.js.map +1 -1
  19. package/lib/package.json +1 -1
  20. package/lib/stores/QueryBuilderState.d.ts.map +1 -1
  21. package/lib/stores/QueryBuilderState.js +3 -4
  22. package/lib/stores/QueryBuilderState.js.map +1 -1
  23. package/lib/stores/QueryBuilderStateBuilder.d.ts +1 -1
  24. package/lib/stores/QueryBuilderStateBuilder.d.ts.map +1 -1
  25. package/lib/stores/QueryBuilderStateBuilder.js +10 -2
  26. package/lib/stores/QueryBuilderStateBuilder.js.map +1 -1
  27. package/lib/stores/QueryBuilderStateHashUtils.d.ts +2 -0
  28. package/lib/stores/QueryBuilderStateHashUtils.d.ts.map +1 -1
  29. package/lib/stores/QueryBuilderStateHashUtils.js +2 -0
  30. package/lib/stores/QueryBuilderStateHashUtils.js.map +1 -1
  31. package/lib/stores/__test-utils__/TEST_DATA__QueryBuilder_Accessors.d.ts +98 -0
  32. package/lib/stores/__test-utils__/TEST_DATA__QueryBuilder_Accessors.d.ts.map +1 -1
  33. package/lib/stores/__test-utils__/TEST_DATA__QueryBuilder_Accessors.js +156 -0
  34. package/lib/stores/__test-utils__/TEST_DATA__QueryBuilder_Accessors.js.map +1 -1
  35. package/lib/stores/filter/QueryBuilderFilterState.d.ts +50 -4
  36. package/lib/stores/filter/QueryBuilderFilterState.d.ts.map +1 -1
  37. package/lib/stores/filter/QueryBuilderFilterState.js +112 -11
  38. package/lib/stores/filter/QueryBuilderFilterState.js.map +1 -1
  39. package/lib/stores/filter/QueryBuilderFilterStateBuilder.d.ts.map +1 -1
  40. package/lib/stores/filter/QueryBuilderFilterStateBuilder.js +17 -3
  41. package/lib/stores/filter/QueryBuilderFilterStateBuilder.js.map +1 -1
  42. package/lib/stores/filter/operators/QueryBuilderFilterOperatorValueSpecificationBuilder.d.ts +6 -0
  43. package/lib/stores/filter/operators/QueryBuilderFilterOperatorValueSpecificationBuilder.d.ts.map +1 -1
  44. package/lib/stores/filter/operators/QueryBuilderFilterOperatorValueSpecificationBuilder.js +35 -9
  45. package/lib/stores/filter/operators/QueryBuilderFilterOperatorValueSpecificationBuilder.js.map +1 -1
  46. package/lib/stores/filter/operators/QueryBuilderFilterOperator_Contain.d.ts.map +1 -1
  47. package/lib/stores/filter/operators/QueryBuilderFilterOperator_Contain.js +2 -4
  48. package/lib/stores/filter/operators/QueryBuilderFilterOperator_Contain.js.map +1 -1
  49. package/lib/stores/filter/operators/QueryBuilderFilterOperator_EndWith.d.ts.map +1 -1
  50. package/lib/stores/filter/operators/QueryBuilderFilterOperator_EndWith.js +2 -4
  51. package/lib/stores/filter/operators/QueryBuilderFilterOperator_EndWith.js.map +1 -1
  52. package/lib/stores/filter/operators/QueryBuilderFilterOperator_Equal.d.ts.map +1 -1
  53. package/lib/stores/filter/operators/QueryBuilderFilterOperator_Equal.js +7 -11
  54. package/lib/stores/filter/operators/QueryBuilderFilterOperator_Equal.js.map +1 -1
  55. package/lib/stores/filter/operators/QueryBuilderFilterOperator_GreaterThan.d.ts.map +1 -1
  56. package/lib/stores/filter/operators/QueryBuilderFilterOperator_GreaterThan.js +7 -11
  57. package/lib/stores/filter/operators/QueryBuilderFilterOperator_GreaterThan.js.map +1 -1
  58. package/lib/stores/filter/operators/QueryBuilderFilterOperator_GreaterThanEqual.d.ts.map +1 -1
  59. package/lib/stores/filter/operators/QueryBuilderFilterOperator_GreaterThanEqual.js +7 -11
  60. package/lib/stores/filter/operators/QueryBuilderFilterOperator_GreaterThanEqual.js.map +1 -1
  61. package/lib/stores/filter/operators/QueryBuilderFilterOperator_In.d.ts.map +1 -1
  62. package/lib/stores/filter/operators/QueryBuilderFilterOperator_In.js +3 -6
  63. package/lib/stores/filter/operators/QueryBuilderFilterOperator_In.js.map +1 -1
  64. package/lib/stores/filter/operators/QueryBuilderFilterOperator_IsEmpty.d.ts +1 -1
  65. package/lib/stores/filter/operators/QueryBuilderFilterOperator_IsEmpty.d.ts.map +1 -1
  66. package/lib/stores/filter/operators/QueryBuilderFilterOperator_IsEmpty.js +5 -3
  67. package/lib/stores/filter/operators/QueryBuilderFilterOperator_IsEmpty.js.map +1 -1
  68. package/lib/stores/filter/operators/QueryBuilderFilterOperator_LessThan.d.ts.map +1 -1
  69. package/lib/stores/filter/operators/QueryBuilderFilterOperator_LessThan.js +7 -11
  70. package/lib/stores/filter/operators/QueryBuilderFilterOperator_LessThan.js.map +1 -1
  71. package/lib/stores/filter/operators/QueryBuilderFilterOperator_LessThanEqual.d.ts.map +1 -1
  72. package/lib/stores/filter/operators/QueryBuilderFilterOperator_LessThanEqual.js +7 -11
  73. package/lib/stores/filter/operators/QueryBuilderFilterOperator_LessThanEqual.js.map +1 -1
  74. package/lib/stores/filter/operators/QueryBuilderFilterOperator_StartWith.d.ts.map +1 -1
  75. package/lib/stores/filter/operators/QueryBuilderFilterOperator_StartWith.js +2 -4
  76. package/lib/stores/filter/operators/QueryBuilderFilterOperator_StartWith.js.map +1 -1
  77. package/lib/stores/milestoning/QueryBuilderMilestoningState.d.ts.map +1 -1
  78. package/lib/stores/milestoning/QueryBuilderMilestoningState.js +9 -6
  79. package/lib/stores/milestoning/QueryBuilderMilestoningState.js.map +1 -1
  80. package/lib/stores/workflows/accessor/AccessorQueryBuilderState.d.ts +3 -3
  81. package/lib/stores/workflows/accessor/AccessorQueryBuilderState.d.ts.map +1 -1
  82. package/lib/stores/workflows/accessor/AccessorQueryBuilderState.js +12 -4
  83. package/lib/stores/workflows/accessor/AccessorQueryBuilderState.js.map +1 -1
  84. package/lib/stores/workflows/dataProduct/DataProductQueryBuilderState.d.ts +2 -1
  85. package/lib/stores/workflows/dataProduct/DataProductQueryBuilderState.d.ts.map +1 -1
  86. package/lib/stores/workflows/dataProduct/DataProductQueryBuilderState.js +36 -6
  87. package/lib/stores/workflows/dataProduct/DataProductQueryBuilderState.js.map +1 -1
  88. package/package.json +5 -5
  89. package/src/components/QueryBuilder.tsx +0 -1
  90. package/src/components/fetch-structure/QueryBuilderTDSPanel.tsx +7 -2
  91. package/src/components/filter/QueryBuilderFilterPanel.tsx +138 -69
  92. package/src/components/result/tds/QueryBuilderTDSResultShared.tsx +4 -1
  93. package/src/index.ts +2 -1
  94. package/src/stores/QueryBuilderState.ts +3 -6
  95. package/src/stores/QueryBuilderStateBuilder.ts +15 -2
  96. package/src/stores/QueryBuilderStateHashUtils.ts +2 -0
  97. package/src/stores/__test-utils__/TEST_DATA__QueryBuilder_Accessors.ts +157 -0
  98. package/src/stores/filter/QueryBuilderFilterState.ts +175 -14
  99. package/src/stores/filter/QueryBuilderFilterStateBuilder.ts +24 -3
  100. package/src/stores/filter/operators/QueryBuilderFilterOperatorValueSpecificationBuilder.ts +55 -24
  101. package/src/stores/filter/operators/QueryBuilderFilterOperator_Contain.ts +2 -5
  102. package/src/stores/filter/operators/QueryBuilderFilterOperator_EndWith.ts +2 -5
  103. package/src/stores/filter/operators/QueryBuilderFilterOperator_Equal.ts +7 -13
  104. package/src/stores/filter/operators/QueryBuilderFilterOperator_GreaterThan.ts +7 -13
  105. package/src/stores/filter/operators/QueryBuilderFilterOperator_GreaterThanEqual.ts +7 -13
  106. package/src/stores/filter/operators/QueryBuilderFilterOperator_In.ts +3 -9
  107. package/src/stores/filter/operators/QueryBuilderFilterOperator_IsEmpty.ts +9 -6
  108. package/src/stores/filter/operators/QueryBuilderFilterOperator_LessThan.ts +7 -13
  109. package/src/stores/filter/operators/QueryBuilderFilterOperator_LessThanEqual.ts +7 -13
  110. package/src/stores/filter/operators/QueryBuilderFilterOperator_StartWith.ts +2 -5
  111. package/src/stores/milestoning/QueryBuilderMilestoningState.ts +13 -7
  112. package/src/stores/workflows/accessor/AccessorQueryBuilderState.ts +17 -3
  113. package/src/stores/workflows/dataProduct/DataProductQueryBuilderState.ts +84 -19
@@ -32,7 +32,10 @@ import {
32
32
  hashArray,
33
33
  isNonNullable,
34
34
  } from '@finos/legend-shared';
35
- import type { QueryBuilderExplorerTreeDragSource } from '../explorer/QueryBuilderExplorerState.js';
35
+ import type {
36
+ QueryBuilderExplorerTreeDragSource,
37
+ QueryBuilderExplorerTreeRelationColumnDragSource,
38
+ } from '../explorer/QueryBuilderExplorerState.js';
36
39
  import { QueryBuilderPropertyExpressionState } from '../QueryBuilderPropertyEditorState.js';
37
40
  import type { QueryBuilderState } from '../QueryBuilderState.js';
38
41
  import {
@@ -46,6 +49,9 @@ import {
46
49
  InstanceValue,
47
50
  SimpleFunctionExpression,
48
51
  matchFunctionName,
52
+ FunctionExpression,
53
+ Multiplicity,
54
+ VariableExpression as VariableExpressionClass,
49
55
  } from '@finos/legend-graph';
50
56
  import { DEFAULT_LAMBDA_VARIABLE_NAME } from '../QueryBuilderConfig.js';
51
57
  import type { QueryBuilderProjectionColumnDragSource } from '../fetch-structure/tds/projection/QueryBuilderProjectionColumnState.js';
@@ -66,6 +72,7 @@ import {
66
72
  import { instanceValue_setValues } from '../shared/ValueSpecificationModifierHelper.js';
67
73
  import { QUERY_BUILDER_SUPPORTED_FUNCTIONS } from '../../graph/QueryBuilderMetaModelConst.js';
68
74
  import type { QueryBuilderVariableDragSource } from '../../components/shared/BasicValueSpecificationEditor.js';
75
+ import { buildPropertyExpressionChain } from '../QueryBuilderValueSpecificationBuilderHelper.js';
69
76
 
70
77
  export enum QUERY_BUILDER_FILTER_DND_TYPE {
71
78
  GROUP_CONDITION = 'QUERY_BUILDER_FILTER_DND_TYPE.GROUP_CONDITION',
@@ -79,6 +86,7 @@ export interface QueryBuilderFilterConditionDragSource {
79
86
 
80
87
  export type QueryBuilderFilterNodeDropTarget =
81
88
  | QueryBuilderExplorerTreeDragSource
89
+ | QueryBuilderExplorerTreeRelationColumnDragSource
82
90
  | QueryBuilderProjectionColumnDragSource
83
91
  | QueryBuilderFilterConditionDragSource;
84
92
 
@@ -229,9 +237,137 @@ export class FilterPropertyExpressionStateConditionValueState extends FilterCond
229
237
  }
230
238
  }
231
239
 
240
+ /**
241
+ * Abstract base class for the left-hand side (source) of a filter condition.
242
+ * This allows filtering on different source types (class properties, relation columns)
243
+ * without operators needing to know the specifics of the source.
244
+ */
245
+ export abstract class FilterConditionSourceState implements Hashable {
246
+ abstract get type(): Type;
247
+ abstract get label(): string;
248
+ abstract get title(): string;
249
+ abstract get isValid(): boolean;
250
+ abstract get requiresExistsHandling(): boolean;
251
+ abstract buildLeftExpression(
252
+ queryBuilderState: QueryBuilderState,
253
+ lambdaParameterName: string,
254
+ ): ValueSpecification;
255
+ abstract get hashCode(): string;
256
+ }
257
+
258
+ export class FilterPropertyExpressionSourceState extends FilterConditionSourceState {
259
+ readonly propertyExpressionState: QueryBuilderPropertyExpressionState;
260
+
261
+ constructor(propertyExpressionState: QueryBuilderPropertyExpressionState) {
262
+ super();
263
+ this.propertyExpressionState = propertyExpressionState;
264
+ }
265
+
266
+ get type(): Type {
267
+ return this.propertyExpressionState.propertyExpression.func.value
268
+ .genericType.value.rawType;
269
+ }
270
+
271
+ get label(): string {
272
+ return this.propertyExpressionState.title;
273
+ }
274
+
275
+ get title(): string {
276
+ return this.propertyExpressionState.title;
277
+ }
278
+
279
+ get isValid(): boolean {
280
+ return this.propertyExpressionState.isValid;
281
+ }
282
+
283
+ get requiresExistsHandling(): boolean {
284
+ return this.propertyExpressionState.requiresExistsHandling;
285
+ }
286
+
287
+ buildLeftExpression(
288
+ queryBuilderState: QueryBuilderState,
289
+ lambdaParameterName: string,
290
+ ): ValueSpecification {
291
+ return guaranteeNonNullable(
292
+ buildPropertyExpressionChain(
293
+ this.propertyExpressionState.propertyExpression,
294
+ queryBuilderState,
295
+ lambdaParameterName,
296
+ ),
297
+ );
298
+ }
299
+
300
+ get hashCode(): string {
301
+ return hashArray([
302
+ QUERY_BUILDER_STATE_HASH_STRUCTURE.FILTER_CONDITION_SOURCE_PROPERTY_EXPRESSION,
303
+ this.propertyExpressionState,
304
+ ]);
305
+ }
306
+ }
307
+
308
+ export class FilterRelationColumnSourceState extends FilterConditionSourceState {
309
+ readonly columnName: string;
310
+ readonly columnType: Type;
311
+
312
+ constructor(columnName: string, columnType: Type) {
313
+ super();
314
+ this.columnName = columnName;
315
+ this.columnType = columnType;
316
+ }
317
+
318
+ get type(): Type {
319
+ return this.columnType;
320
+ }
321
+
322
+ get label(): string {
323
+ return this.columnName;
324
+ }
325
+
326
+ get title(): string {
327
+ return this.columnName;
328
+ }
329
+
330
+ get isValid(): boolean {
331
+ return true;
332
+ }
333
+
334
+ get requiresExistsHandling(): boolean {
335
+ return false;
336
+ }
337
+
338
+ buildLeftExpression(
339
+ queryBuilderState: QueryBuilderState,
340
+ lambdaParameterName: string,
341
+ ): ValueSpecification {
342
+ const relationType = queryBuilderState.sourceRelationType;
343
+ if (relationType) {
344
+ const col = guaranteeNonNullable(
345
+ relationType.columns.find((c) => c.name === this.columnName),
346
+ `Can't find column '${this.columnName}' in relation`,
347
+ );
348
+ const funcExp = new FunctionExpression(col.name);
349
+ funcExp.func = col;
350
+ funcExp.parametersValues = [
351
+ new VariableExpressionClass(lambdaParameterName, Multiplicity.ONE),
352
+ ];
353
+ return funcExp;
354
+ }
355
+ throw new IllegalStateError(
356
+ `Can't build expression for relation column '${this.columnName}': no source relation type`,
357
+ );
358
+ }
359
+
360
+ get hashCode(): string {
361
+ return hashArray([
362
+ QUERY_BUILDER_STATE_HASH_STRUCTURE.FILTER_CONDITION_SOURCE_RELATION_COLUMN,
363
+ this.columnName,
364
+ ]);
365
+ }
366
+ }
367
+
232
368
  export class FilterConditionState implements Hashable {
233
369
  readonly filterState: QueryBuilderFilterState;
234
- propertyExpressionState: QueryBuilderPropertyExpressionState;
370
+ sourceState: FilterConditionSourceState;
235
371
  operator!: QueryBuilderFilterOperator;
236
372
  rightConditionValue?: FilterConditionValueState | undefined;
237
373
  existsLambdaParamNames: string[] = [];
@@ -240,11 +376,11 @@ export class FilterConditionState implements Hashable {
240
376
 
241
377
  constructor(
242
378
  filterState: QueryBuilderFilterState,
243
- propertyExpression: AbstractPropertyExpression,
379
+ source: AbstractPropertyExpression | FilterConditionSourceState,
244
380
  operator?: QueryBuilderFilterOperator,
245
381
  ) {
246
382
  makeObservable(this, {
247
- propertyExpressionState: observable,
383
+ sourceState: observable,
248
384
  operator: observable,
249
385
  rightConditionValue: observable,
250
386
  existsLambdaParamNames: observable,
@@ -261,10 +397,16 @@ export class FilterConditionState implements Hashable {
261
397
  });
262
398
 
263
399
  this.filterState = filterState;
264
- this.propertyExpressionState = new QueryBuilderPropertyExpressionState(
265
- filterState.queryBuilderState,
266
- propertyExpression,
267
- );
400
+ if (source instanceof AbstractPropertyExpression) {
401
+ this.sourceState = new FilterPropertyExpressionSourceState(
402
+ new QueryBuilderPropertyExpressionState(
403
+ filterState.queryBuilderState,
404
+ source,
405
+ ),
406
+ );
407
+ } else {
408
+ this.sourceState = source;
409
+ }
268
410
 
269
411
  // operator
270
412
  if (operator) {
@@ -272,7 +414,7 @@ export class FilterConditionState implements Hashable {
272
414
  } else {
273
415
  assertTrue(
274
416
  this.operators.length !== 0,
275
- `Can't find an operator for property '${this.propertyExpressionState.path}': no operators registered`,
417
+ `Can't find an operator for property '${this.sourceState.title}': no operators registered`,
276
418
  );
277
419
  this.operator = this.operators[0] as QueryBuilderFilterOperator;
278
420
  }
@@ -281,6 +423,25 @@ export class FilterConditionState implements Hashable {
281
423
  );
282
424
  }
283
425
 
426
+ /**
427
+ * Backward-compatible accessor for the property expression state.
428
+ * Only valid when the source is a property expression.
429
+ */
430
+ get propertyExpressionState(): QueryBuilderPropertyExpressionState {
431
+ return guaranteeType(
432
+ this.sourceState,
433
+ FilterPropertyExpressionSourceState,
434
+ `Filter condition source is not a property expression`,
435
+ ).propertyExpressionState;
436
+ }
437
+
438
+ /**
439
+ * The raw type of the left-hand side of the filter condition.
440
+ */
441
+ get leftConditionType(): Type {
442
+ return this.sourceState.type;
443
+ }
444
+
284
445
  get operators(): QueryBuilderFilterOperator[] {
285
446
  return this.filterState.operators.filter((op) =>
286
447
  op.isCompatibleWithFilterConditionProperty(this),
@@ -412,7 +573,7 @@ export class FilterConditionState implements Hashable {
412
573
  get hashCode(): string {
413
574
  return hashArray([
414
575
  QUERY_BUILDER_STATE_HASH_STRUCTURE.FILTER_CONDITION_STATE,
415
- this.propertyExpressionState,
576
+ this.sourceState,
416
577
  this.rightConditionValue ?? '',
417
578
  this.operator,
418
579
  ]);
@@ -636,7 +797,7 @@ export class QueryBuilderFilterTreeConditionNodeData
636
797
  }
637
798
 
638
799
  get dragPreviewLabel(): string {
639
- return this.condition.propertyExpressionState.title;
800
+ return this.condition.sourceState.title;
640
801
  }
641
802
 
642
803
  get hashCode(): string {
@@ -1181,7 +1342,7 @@ export class QueryBuilderFilterState
1181
1342
  ): boolean {
1182
1343
  return (
1183
1344
  node instanceof QueryBuilderFilterTreeConditionNodeData &&
1184
- !node.condition.propertyExpressionState.isValid
1345
+ !node.condition.sourceState.isValid
1185
1346
  );
1186
1347
  }
1187
1348
 
@@ -1212,12 +1373,12 @@ export class QueryBuilderFilterState
1212
1373
  if (node instanceof QueryBuilderFilterTreeConditionNodeData) {
1213
1374
  if (this.isInvalidValueSpecFilterValue(node)) {
1214
1375
  validationIssues.push(
1215
- `Filter value for ${node.condition.propertyExpressionState.title} is missing or invalid`,
1376
+ `Filter value for ${node.condition.sourceState.title} is missing or invalid`,
1216
1377
  );
1217
1378
  }
1218
1379
  if (this.isInvalidFilterPropertyExpressionState(node)) {
1219
1380
  validationIssues.push(
1220
- `Derived property parameter value for ${node.condition.propertyExpressionState.title} is missing or invalid`,
1381
+ `Derived property parameter value for ${node.condition.sourceState.title} is missing or invalid`,
1221
1382
  );
1222
1383
  }
1223
1384
  if (
@@ -23,6 +23,7 @@ import {
23
23
  SimpleFunctionExpression,
24
24
  type ValueSpecification,
25
25
  VariableExpression,
26
+ FunctionExpression,
26
27
  } from '@finos/legend-graph';
27
28
  import {
28
29
  assertTrue,
@@ -42,6 +43,7 @@ import {
42
43
  QueryBuilderFilterTreeGroupNodeData,
43
44
  QueryBuilderFilterTreeExistsNodeData,
44
45
  QueryBuilderFilterTreeOperationNodeData,
46
+ FilterRelationColumnSourceState,
45
47
  } from './QueryBuilderFilterState.js';
46
48
 
47
49
  const getPropertyExpressionChainVariable = (
@@ -321,9 +323,28 @@ const processFilterTree = (
321
323
  operator.buildFilterConditionState(filterState, expression),
322
324
  );
323
325
  if (filterConditionState) {
324
- const variableName = getPropertyExpressionChainVariable(
325
- filterConditionState.propertyExpressionState.propertyExpression,
326
- ).name;
326
+ // Extract the variable name from the filter condition's left side.
327
+ // For relation column sources, the variable comes from the FunctionExpression's parameter;
328
+ // for property expression sources, it comes from the property expression chain.
329
+ let variableName: string;
330
+ if (
331
+ filterConditionState.sourceState instanceof
332
+ FilterRelationColumnSourceState
333
+ ) {
334
+ const leftSide = expression.parametersValues[0];
335
+ const varExpr = guaranteeType(
336
+ leftSide instanceof FunctionExpression
337
+ ? leftSide.parametersValues[0]
338
+ : undefined,
339
+ VariableExpression,
340
+ `Can't process filter expression: relation column filter must reference a variable`,
341
+ );
342
+ variableName = varExpr.name;
343
+ } else {
344
+ variableName = getPropertyExpressionChainVariable(
345
+ filterConditionState.propertyExpressionState.propertyExpression,
346
+ ).name;
347
+ }
327
348
  const parentLambdaVariableName =
328
349
  parentNode instanceof QueryBuilderFilterTreeOperationNodeData &&
329
350
  parentNode.lambdaParameterName
@@ -22,15 +22,14 @@ import {
22
22
  AbstractPropertyExpression,
23
23
  SimpleFunctionExpression,
24
24
  LambdaFunction,
25
+ FunctionExpression,
26
+ RelationColumn,
25
27
  } from '@finos/legend-graph';
26
- import {
27
- guaranteeType,
28
- guaranteeNonNullable,
29
- assertTrue,
30
- } from '@finos/legend-shared';
28
+ import { guaranteeType, assertTrue } from '@finos/legend-shared';
31
29
  import {
32
30
  FilterConditionState,
33
31
  FilterPropertyExpressionStateConditionValueState,
32
+ FilterRelationColumnSourceState,
34
33
  FilterValueSpecConditionValueState,
35
34
  type QueryBuilderFilterState,
36
35
  } from '../QueryBuilderFilterState.js';
@@ -48,12 +47,13 @@ export const buildFilterConditionExpression = (
48
47
  const expression = new SimpleFunctionExpression(
49
48
  extractElementNameFromPath(operatorFunctionFullPath),
50
49
  );
51
- const propertyExpression = buildPropertyExpressionChain(
52
- filterConditionState.propertyExpressionState.propertyExpression,
53
- filterConditionState.propertyExpressionState.queryBuilderState,
54
- lambdaParameterName ?? filterConditionState.filterState.lambdaParameterName,
50
+ const resolvedLambdaParameterName =
51
+ lambdaParameterName ?? filterConditionState.filterState.lambdaParameterName;
52
+ const leftExpression = filterConditionState.sourceState.buildLeftExpression(
53
+ filterConditionState.filterState.queryBuilderState,
54
+ resolvedLambdaParameterName,
55
55
  );
56
- expression.parametersValues.push(guaranteeNonNullable(propertyExpression));
56
+ expression.parametersValues.push(leftExpression);
57
57
  // NOTE: there are simple operators which do not require any params (e.g. isEmpty)
58
58
  if (
59
59
  filterConditionState.rightConditionValue &&
@@ -72,15 +72,35 @@ export const buildFilterConditionExpression = (
72
72
  const rightConditionPropertyExpression = buildPropertyExpressionChain(
73
73
  filterConditionState.rightConditionValue.propertyExpressionState
74
74
  .propertyExpression,
75
- filterConditionState.propertyExpressionState.queryBuilderState,
76
- lambdaParameterName ??
77
- filterConditionState.filterState.lambdaParameterName,
75
+ filterConditionState.filterState.queryBuilderState,
76
+ resolvedLambdaParameterName,
78
77
  );
79
78
  expression.parametersValues.push(rightConditionPropertyExpression);
80
79
  }
81
80
  return expression;
82
81
  };
83
82
 
83
+ /**
84
+ * Extracts the raw type path from the left-hand side of a filter expression.
85
+ * Handles both AbstractPropertyExpression (class properties) and
86
+ * FunctionExpression with RelationColumn (relation column properties).
87
+ */
88
+ export const getFilterExpressionLeftSideTypePath = (
89
+ expression: SimpleFunctionExpression,
90
+ ): string | undefined => {
91
+ const leftSide = expression.parametersValues[0];
92
+ if (leftSide instanceof AbstractPropertyExpression) {
93
+ return leftSide.func.value.genericType.value.rawType.path;
94
+ }
95
+ if (
96
+ leftSide instanceof FunctionExpression &&
97
+ leftSide.func instanceof RelationColumn
98
+ ) {
99
+ return leftSide.func.genericType.value.rawType.path;
100
+ }
101
+ return undefined;
102
+ };
103
+
84
104
  export const buildFilterConditionState = (
85
105
  filterState: QueryBuilderFilterState,
86
106
  expression: SimpleFunctionExpression,
@@ -109,18 +129,29 @@ export const buildFilterConditionState = (
109
129
  )}() expects ${hasNoValue ? 'no argument' : '1 argument'}`,
110
130
  );
111
131
 
112
- const propertyExpression = guaranteeType(
113
- expression.parametersValues[0],
114
- AbstractPropertyExpression,
115
- `Can't process ${extractElementNameFromPath(
116
- operatorFunctionFullPath,
117
- )}() expression: expects property expression in lambda body`,
118
- );
132
+ const leftSide = expression.parametersValues[0];
133
+ if (leftSide instanceof AbstractPropertyExpression) {
134
+ filterConditionState = new FilterConditionState(filterState, leftSide);
135
+ } else if (
136
+ leftSide instanceof FunctionExpression &&
137
+ leftSide.func instanceof RelationColumn
138
+ ) {
139
+ const col = leftSide.func;
140
+ filterConditionState = new FilterConditionState(
141
+ filterState,
142
+ new FilterRelationColumnSourceState(
143
+ col.name,
144
+ col.genericType.value.rawType,
145
+ ),
146
+ );
147
+ } else {
148
+ throw new Error(
149
+ `Can't process ${extractElementNameFromPath(
150
+ operatorFunctionFullPath,
151
+ )}() expression: expects property expression or relation column in lambda body`,
152
+ );
153
+ }
119
154
 
120
- filterConditionState = new FilterConditionState(
121
- filterState,
122
- propertyExpression,
123
- );
124
155
  mainExpressionWithOperator = expression;
125
156
  } else if (
126
157
  matchFunctionName(
@@ -55,8 +55,7 @@ export class QueryBuilderFilterOperator_Contain
55
55
  ): boolean {
56
56
  return (
57
57
  getStandardPrimitiveTypeEquivalent(
58
- filterConditionState.propertyExpressionState.propertyExpression.func
59
- .value.genericType.value.rawType,
58
+ filterConditionState.leftConditionType,
60
59
  ) === PRIMITIVE_TYPE.STRING
61
60
  );
62
61
  }
@@ -78,9 +77,7 @@ export class QueryBuilderFilterOperator_Contain
78
77
  getDefaultFilterConditionValue(
79
78
  filterConditionState: FilterConditionState,
80
79
  ): ValueSpecification | undefined {
81
- const propertyType =
82
- filterConditionState.propertyExpressionState.propertyExpression.func.value
83
- .genericType.value.rawType;
80
+ const propertyType = filterConditionState.leftConditionType;
84
81
  switch (propertyType.path) {
85
82
  case PRIMITIVE_TYPE.STRING: {
86
83
  return buildDefaultInstanceValue(
@@ -55,8 +55,7 @@ export class QueryBuilderFilterOperator_EndWith
55
55
  ): boolean {
56
56
  return (
57
57
  getStandardPrimitiveTypeEquivalent(
58
- filterConditionState.propertyExpressionState.propertyExpression.func
59
- .value.genericType.value.rawType,
58
+ filterConditionState.leftConditionType,
60
59
  ) === PRIMITIVE_TYPE.STRING
61
60
  );
62
61
  }
@@ -78,9 +77,7 @@ export class QueryBuilderFilterOperator_EndWith
78
77
  getDefaultFilterConditionValue(
79
78
  filterConditionState: FilterConditionState,
80
79
  ): ValueSpecification | undefined {
81
- const propertyType =
82
- filterConditionState.propertyExpressionState.propertyExpression.func.value
83
- .genericType.value.rawType;
80
+ const propertyType = filterConditionState.leftConditionType;
84
81
  switch (propertyType.path) {
85
82
  case PRIMITIVE_TYPE.STRING: {
86
83
  return buildDefaultInstanceValue(
@@ -22,7 +22,6 @@ import { QueryBuilderFilterOperator } from '../QueryBuilderFilterOperator.js';
22
22
  import {
23
23
  type ValueSpecification,
24
24
  type SimpleFunctionExpression,
25
- type AbstractPropertyExpression,
26
25
  Enumeration,
27
26
  PRIMITIVE_TYPE,
28
27
  } from '@finos/legend-graph';
@@ -30,6 +29,7 @@ import { type Hashable, hashArray } from '@finos/legend-shared';
30
29
  import {
31
30
  buildFilterConditionState,
32
31
  buildFilterConditionExpression,
32
+ getFilterExpressionLeftSideTypePath,
33
33
  } from './QueryBuilderFilterOperatorValueSpecificationBuilder.js';
34
34
  import { QUERY_BUILDER_SUPPORTED_FUNCTIONS } from '../../../graph/QueryBuilderMetaModelConst.js';
35
35
  import {
@@ -52,9 +52,7 @@ export class QueryBuilderFilterOperator_Equal
52
52
  isCompatibleWithFilterConditionProperty(
53
53
  filterConditionState: FilterConditionState,
54
54
  ): boolean {
55
- const propertyType =
56
- filterConditionState.propertyExpressionState.propertyExpression.func.value
57
- .genericType.value.rawType;
55
+ const propertyType = filterConditionState.leftConditionType;
58
56
  const normalizedType = getStandardPrimitiveTypeEquivalent(propertyType);
59
57
  return (
60
58
  (normalizedType !== undefined &&
@@ -84,17 +82,14 @@ export class QueryBuilderFilterOperator_Equal
84
82
  !filterConditionState.rightConditionValue.isCollection
85
83
  ? filterConditionState.rightConditionValue.type
86
84
  : undefined,
87
- filterConditionState.propertyExpressionState.propertyExpression.func.value
88
- .genericType.value.rawType,
85
+ filterConditionState.leftConditionType,
89
86
  );
90
87
  }
91
88
 
92
89
  getDefaultFilterConditionValue(
93
90
  filterConditionState: FilterConditionState,
94
91
  ): ValueSpecification | undefined {
95
- const propertyType =
96
- filterConditionState.propertyExpressionState.propertyExpression.func.value
97
- .genericType.value.rawType;
92
+ const propertyType = filterConditionState.leftConditionType;
98
93
  return buildDefaultInstanceValue(
99
94
  filterConditionState.filterState.queryBuilderState.graphManagerState
100
95
  .graph,
@@ -111,8 +106,7 @@ export class QueryBuilderFilterOperator_Equal
111
106
  ): ValueSpecification {
112
107
  return buildFilterConditionExpression(
113
108
  filterConditionState,
114
- filterConditionState.propertyExpressionState.propertyExpression.func.value
115
- .genericType.value.rawType.path === PRIMITIVE_TYPE.DATETIME &&
109
+ filterConditionState.leftConditionType.path === PRIMITIVE_TYPE.DATETIME &&
116
110
  filterConditionState.rightConditionValue?.type?.path !==
117
111
  PRIMITIVE_TYPE.DATETIME
118
112
  ? QUERY_BUILDER_SUPPORTED_FUNCTIONS.IS_ON_DAY
@@ -128,8 +122,8 @@ export class QueryBuilderFilterOperator_Equal
128
122
  return buildFilterConditionState(
129
123
  filterState,
130
124
  expression,
131
- (expression.parametersValues[0] as AbstractPropertyExpression).func.value
132
- .genericType.value.rawType.path === PRIMITIVE_TYPE.DATETIME &&
125
+ getFilterExpressionLeftSideTypePath(expression) ===
126
+ PRIMITIVE_TYPE.DATETIME &&
133
127
  expression.parametersValues[1]?.genericType?.value.rawType.path !==
134
128
  PRIMITIVE_TYPE.DATETIME
135
129
  ? QUERY_BUILDER_SUPPORTED_FUNCTIONS.IS_ON_DAY
@@ -22,7 +22,6 @@ import { QueryBuilderFilterOperator } from '../QueryBuilderFilterOperator.js';
22
22
  import {
23
23
  type ValueSpecification,
24
24
  type SimpleFunctionExpression,
25
- type AbstractPropertyExpression,
26
25
  PRIMITIVE_TYPE,
27
26
  } from '@finos/legend-graph';
28
27
  import {
@@ -33,6 +32,7 @@ import {
33
32
  import {
34
33
  buildFilterConditionState,
35
34
  buildFilterConditionExpression,
35
+ getFilterExpressionLeftSideTypePath,
36
36
  } from './QueryBuilderFilterOperatorValueSpecificationBuilder.js';
37
37
  import { QUERY_BUILDER_SUPPORTED_FUNCTIONS } from '../../../graph/QueryBuilderMetaModelConst.js';
38
38
  import {
@@ -53,9 +53,7 @@ export class QueryBuilderFilterOperator_GreaterThan
53
53
  isCompatibleWithFilterConditionProperty(
54
54
  filterConditionState: FilterConditionState,
55
55
  ): boolean {
56
- const propertyType =
57
- filterConditionState.propertyExpressionState.propertyExpression.func.value
58
- .genericType.value.rawType;
56
+ const propertyType = filterConditionState.leftConditionType;
59
57
  const normalizedType = getStandardPrimitiveTypeEquivalent(propertyType);
60
58
  return (
61
59
  normalizedType !== undefined &&
@@ -81,17 +79,14 @@ export class QueryBuilderFilterOperator_GreaterThan
81
79
  !filterConditionState.rightConditionValue.isCollection
82
80
  ? filterConditionState.rightConditionValue.type
83
81
  : undefined,
84
- filterConditionState.propertyExpressionState.propertyExpression.func.value
85
- .genericType.value.rawType,
82
+ filterConditionState.leftConditionType,
86
83
  );
87
84
  }
88
85
 
89
86
  getDefaultFilterConditionValue(
90
87
  filterConditionState: FilterConditionState,
91
88
  ): ValueSpecification | undefined {
92
- const propertyType =
93
- filterConditionState.propertyExpressionState.propertyExpression.func.value
94
- .genericType.value.rawType;
89
+ const propertyType = filterConditionState.leftConditionType;
95
90
  switch (propertyType.path) {
96
91
  case PRIMITIVE_TYPE.NUMBER:
97
92
  case PRIMITIVE_TYPE.DECIMAL:
@@ -122,8 +117,7 @@ export class QueryBuilderFilterOperator_GreaterThan
122
117
  ): ValueSpecification {
123
118
  return buildFilterConditionExpression(
124
119
  filterConditionState,
125
- filterConditionState.propertyExpressionState.propertyExpression.func.value
126
- .genericType.value.rawType.path === PRIMITIVE_TYPE.DATETIME &&
120
+ filterConditionState.leftConditionType.path === PRIMITIVE_TYPE.DATETIME &&
127
121
  filterConditionState.rightConditionValue?.type?.path !==
128
122
  PRIMITIVE_TYPE.DATETIME
129
123
  ? QUERY_BUILDER_SUPPORTED_FUNCTIONS.IS_AFTER_DAY
@@ -139,8 +133,8 @@ export class QueryBuilderFilterOperator_GreaterThan
139
133
  return buildFilterConditionState(
140
134
  filterState,
141
135
  expression,
142
- (expression.parametersValues[0] as AbstractPropertyExpression).func.value
143
- .genericType.value.rawType.path === PRIMITIVE_TYPE.DATETIME &&
136
+ getFilterExpressionLeftSideTypePath(expression) ===
137
+ PRIMITIVE_TYPE.DATETIME &&
144
138
  expression.parametersValues[1]?.genericType?.value.rawType.path !==
145
139
  PRIMITIVE_TYPE.DATETIME
146
140
  ? QUERY_BUILDER_SUPPORTED_FUNCTIONS.IS_AFTER_DAY