@finos/legend-query-builder 4.18.17 → 4.18.18

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 (42) hide show
  1. package/lib/components/explorer/QueryBuilderRelationExplorerPanel.d.ts.map +1 -1
  2. package/lib/components/explorer/QueryBuilderRelationExplorerPanel.js +14 -2
  3. package/lib/components/explorer/QueryBuilderRelationExplorerPanel.js.map +1 -1
  4. package/lib/components/filter/QueryBuilderFilterPanel.d.ts.map +1 -1
  5. package/lib/components/filter/QueryBuilderFilterPanel.js +23 -3
  6. package/lib/components/filter/QueryBuilderFilterPanel.js.map +1 -1
  7. package/lib/components/sql-playground/SQLPlaygroundGrid.d.ts.map +1 -1
  8. package/lib/components/sql-playground/SQLPlaygroundGrid.js +4 -1
  9. package/lib/components/sql-playground/SQLPlaygroundGrid.js.map +1 -1
  10. package/lib/data-access-overview.css +1 -1
  11. package/lib/index.css +16 -0
  12. package/lib/package.json +1 -1
  13. package/lib/stores/fetch-structure/tds/post-filter/operators/QueryBuilderPostFilterOperatorValueSpecificationBuilder.d.ts.map +1 -1
  14. package/lib/stores/fetch-structure/tds/post-filter/operators/QueryBuilderPostFilterOperatorValueSpecificationBuilder.js +12 -0
  15. package/lib/stores/fetch-structure/tds/post-filter/operators/QueryBuilderPostFilterOperatorValueSpecificationBuilder.js.map +1 -1
  16. package/lib/stores/fetch-structure/tds/post-filter/operators/QueryBuilderPostFilterOperator_IsEmpty.d.ts +4 -2
  17. package/lib/stores/fetch-structure/tds/post-filter/operators/QueryBuilderPostFilterOperator_IsEmpty.d.ts.map +1 -1
  18. package/lib/stores/fetch-structure/tds/post-filter/operators/QueryBuilderPostFilterOperator_IsEmpty.js +50 -6
  19. package/lib/stores/fetch-structure/tds/post-filter/operators/QueryBuilderPostFilterOperator_IsEmpty.js.map +1 -1
  20. package/lib/stores/filter/QueryBuilderFilterState.d.ts +4 -2
  21. package/lib/stores/filter/QueryBuilderFilterState.d.ts.map +1 -1
  22. package/lib/stores/filter/QueryBuilderFilterState.js +8 -1
  23. package/lib/stores/filter/QueryBuilderFilterState.js.map +1 -1
  24. package/lib/stores/filter/QueryBuilderFilterStateBuilder.d.ts.map +1 -1
  25. package/lib/stores/filter/QueryBuilderFilterStateBuilder.js +19 -3
  26. package/lib/stores/filter/QueryBuilderFilterStateBuilder.js.map +1 -1
  27. package/lib/stores/filter/operators/QueryBuilderFilterOperatorValueSpecificationBuilder.d.ts.map +1 -1
  28. package/lib/stores/filter/operators/QueryBuilderFilterOperatorValueSpecificationBuilder.js +1 -1
  29. package/lib/stores/filter/operators/QueryBuilderFilterOperatorValueSpecificationBuilder.js.map +1 -1
  30. package/lib/stores/filter/operators/QueryBuilderFilterOperator_IsEmpty.d.ts.map +1 -1
  31. package/lib/stores/filter/operators/QueryBuilderFilterOperator_IsEmpty.js +14 -5
  32. package/lib/stores/filter/operators/QueryBuilderFilterOperator_IsEmpty.js.map +1 -1
  33. package/package.json +6 -6
  34. package/src/components/explorer/QueryBuilderRelationExplorerPanel.tsx +22 -0
  35. package/src/components/filter/QueryBuilderFilterPanel.tsx +46 -0
  36. package/src/components/sql-playground/SQLPlaygroundGrid.tsx +15 -2
  37. package/src/stores/fetch-structure/tds/post-filter/operators/QueryBuilderPostFilterOperatorValueSpecificationBuilder.ts +13 -0
  38. package/src/stores/fetch-structure/tds/post-filter/operators/QueryBuilderPostFilterOperator_IsEmpty.ts +91 -6
  39. package/src/stores/filter/QueryBuilderFilterState.ts +13 -1
  40. package/src/stores/filter/QueryBuilderFilterStateBuilder.ts +24 -3
  41. package/src/stores/filter/operators/QueryBuilderFilterOperatorValueSpecificationBuilder.ts +1 -0
  42. package/src/stores/filter/operators/QueryBuilderFilterOperator_IsEmpty.ts +16 -10
@@ -40,6 +40,7 @@ import {
40
40
  } from '../../../../../graph/QueryBuilderMetaModelConst.js';
41
41
  import type { QueryBuilderTDSColumnState } from '../../QueryBuilderTDSColumnState.js';
42
42
  import { getTDSColumnDerivedProperyFromType } from '../../QueryBuilderTDSHelper.js';
43
+ import { QueryBuilderRelationColumnProjectionColumnState } from '../../projection/QueryBuilderProjectionColumnState.js';
43
44
 
44
45
  export const buildtdsPropertyExpressionFromColState = (
45
46
  filterConditionState: PostFilterConditionState,
@@ -64,6 +65,18 @@ export const buildtdsPropertyExpressionFromColState = (
64
65
  _funcExp.func = col;
65
66
  _funcExp.parametersValues = [variableName];
66
67
  return _funcExp;
68
+ } else if (
69
+ colState instanceof QueryBuilderRelationColumnProjectionColumnState
70
+ ) {
71
+ // Fallback when parent lambda isn't available yet (e.g. when an operator
72
+ // is asked to build its expression in isolation): the projection state
73
+ // already carries the `RelationColumn`, so build the column accessor
74
+ // directly.
75
+ const col = colState.column;
76
+ const _funcExp = new FunctionExpression(col.name);
77
+ _funcExp.func = col;
78
+ _funcExp.parametersValues = [variableName];
79
+ return _funcExp;
67
80
  } else {
68
81
  const tdsPropertyExpression = new AbstractPropertyExpression('');
69
82
  let tdsDerivedPropertyName: TDS_COLUMN_GETTER;
@@ -17,25 +17,38 @@
17
17
  import {
18
18
  type Type,
19
19
  type ValueSpecification,
20
- type FunctionExpression,
20
+ FunctionExpression,
21
21
  type LambdaFunction,
22
22
  Enumeration,
23
23
  PrimitiveType,
24
24
  PrecisePrimitiveType,
25
+ SimpleFunctionExpression,
26
+ matchFunctionName,
25
27
  } from '@finos/legend-graph';
26
28
  import { QueryBuilderPostFilterOperator } from '../QueryBuilderPostFilterOperator.js';
27
29
  import { buildPostFilterConditionState } from '../QueryBuilderPostFilterStateBuilder.js';
28
30
  import {
29
- type PostFilterConditionState,
30
31
  type QueryBuilderPostFilterState,
32
+ PostFilterConditionState,
31
33
  PostFilterValueSpecConditionValueState,
32
34
  } from '../QueryBuilderPostFilterState.js';
33
- import { QueryBuilderSimpleProjectionColumnState } from '../../projection/QueryBuilderProjectionColumnState.js';
35
+ import {
36
+ QueryBuilderRelationColumnProjectionColumnState,
37
+ QueryBuilderSimpleProjectionColumnState,
38
+ } from '../../projection/QueryBuilderProjectionColumnState.js';
34
39
  import { buildPostFilterConditionExpressionHelper } from './QueryBuilderPostFilterOperatorValueSpecificationBuilder.js';
35
- import { isPropertyExpressionChainOptional } from '../../../../QueryBuilderValueSpecificationHelper.js';
40
+ import { getTDSColumnState } from '../../QueryBuilderTDSHelper.js';
41
+ import {
42
+ buildNotExpression,
43
+ isPropertyExpressionChainOptional,
44
+ unwrapNotExpression,
45
+ } from '../../../../QueryBuilderValueSpecificationHelper.js';
36
46
  import { type Hashable, hashArray } from '@finos/legend-shared';
37
47
  import { QUERY_BUILDER_STATE_HASH_STRUCTURE } from '../../../../QueryBuilderStateHashUtils.js';
38
- import { TDS_COLUMN_GETTER } from '../../../../../graph/QueryBuilderMetaModelConst.js';
48
+ import {
49
+ QUERY_BUILDER_SUPPORTED_FUNCTIONS,
50
+ TDS_COLUMN_GETTER,
51
+ } from '../../../../../graph/QueryBuilderMetaModelConst.js';
39
52
 
40
53
  export class QueryBuilderPostFilterOperator_IsEmpty
41
54
  extends QueryBuilderPostFilterOperator
@@ -84,6 +97,15 @@ export class QueryBuilderPostFilterOperator_IsEmpty
84
97
  .propertyExpression,
85
98
  );
86
99
  }
100
+ if (
101
+ postFilterState.leftConditionValue instanceof
102
+ QueryBuilderRelationColumnProjectionColumnState
103
+ ) {
104
+ return (
105
+ postFilterState.leftConditionValue.column.multiplicity.lowerBound ===
106
+ 0
107
+ );
108
+ }
87
109
  return true;
88
110
  }
89
111
  return false;
@@ -99,10 +121,18 @@ export class QueryBuilderPostFilterOperator_IsEmpty
99
121
  postFilterConditionState: PostFilterConditionState,
100
122
  parentExpression: LambdaFunction | undefined,
101
123
  ): ValueSpecification | undefined {
124
+ // For relation-column projections there is no `TDSRow.isNull` derived
125
+ // property to lean on, so wrap the column expression in `isEmpty(...)`
126
+ // (mirrors how the where-filter `is empty` operator builds its lambda).
127
+ const operatorFunctionFullPath =
128
+ postFilterConditionState.leftConditionValue instanceof
129
+ QueryBuilderRelationColumnProjectionColumnState
130
+ ? QUERY_BUILDER_SUPPORTED_FUNCTIONS.IS_EMPTY
131
+ : undefined;
102
132
  return buildPostFilterConditionExpressionHelper(
103
133
  postFilterConditionState,
104
134
  this,
105
- undefined,
135
+ operatorFunctionFullPath,
106
136
  parentExpression,
107
137
  );
108
138
  }
@@ -111,6 +141,24 @@ export class QueryBuilderPostFilterOperator_IsEmpty
111
141
  postFilterState: QueryBuilderPostFilterState,
112
142
  expression: FunctionExpression,
113
143
  ): PostFilterConditionState | undefined {
144
+ // Round-trip: handle the relation-column variant emitted as
145
+ // `isEmpty($row.<col>)` (no TDSRow getter available).
146
+ if (
147
+ expression instanceof SimpleFunctionExpression &&
148
+ matchFunctionName(
149
+ expression.functionName,
150
+ QUERY_BUILDER_SUPPORTED_FUNCTIONS.IS_EMPTY,
151
+ ) &&
152
+ expression.parametersValues.length === 1 &&
153
+ expression.parametersValues[0] instanceof FunctionExpression
154
+ ) {
155
+ const columnFuncExp = expression.parametersValues[0];
156
+ const columnState = getTDSColumnState(
157
+ postFilterState.tdsState,
158
+ columnFuncExp.functionName,
159
+ );
160
+ return new PostFilterConditionState(postFilterState, columnState, this);
161
+ }
114
162
  return buildPostFilterConditionState(
115
163
  postFilterState,
116
164
  expression,
@@ -135,6 +183,43 @@ export class QueryBuilderPostFilterOperator_IsNotEmpty extends QueryBuilderPostF
135
183
  return TDS_COLUMN_GETTER.IS_NOT_NULL;
136
184
  }
137
185
 
186
+ override buildPostFilterConditionExpression(
187
+ postFilterConditionState: PostFilterConditionState,
188
+ parentExpression: LambdaFunction | undefined,
189
+ ): ValueSpecification | undefined {
190
+ // For relation-column projections, `is not empty` has no TDS column getter
191
+ // shortcut either; build it as `not(isEmpty(...))` instead.
192
+ if (
193
+ postFilterConditionState.leftConditionValue instanceof
194
+ QueryBuilderRelationColumnProjectionColumnState
195
+ ) {
196
+ const inner = super.buildPostFilterConditionExpression(
197
+ postFilterConditionState,
198
+ parentExpression,
199
+ );
200
+ return inner ? buildNotExpression(inner) : undefined;
201
+ }
202
+ return super.buildPostFilterConditionExpression(
203
+ postFilterConditionState,
204
+ parentExpression,
205
+ );
206
+ }
207
+
208
+ override buildPostFilterConditionState(
209
+ postFilterState: QueryBuilderPostFilterState,
210
+ expression: FunctionExpression,
211
+ ): PostFilterConditionState | undefined {
212
+ // Round-trip: unwrap a leading `not(...)` (relation-column variant) before
213
+ // delegating to the IsEmpty builder.
214
+ if (expression instanceof SimpleFunctionExpression) {
215
+ const inner = unwrapNotExpression(expression);
216
+ if (inner) {
217
+ return super.buildPostFilterConditionState(postFilterState, inner);
218
+ }
219
+ }
220
+ return super.buildPostFilterConditionState(postFilterState, expression);
221
+ }
222
+
138
223
  override get hashCode(): string {
139
224
  return hashArray([
140
225
  QUERY_BUILDER_STATE_HASH_STRUCTURE.POST_FILTER_OPERATOR_IS_NOT_EMPTY,
@@ -308,11 +308,21 @@ export class FilterPropertyExpressionSourceState extends FilterConditionSourceSt
308
308
  export class FilterRelationColumnSourceState extends FilterConditionSourceState {
309
309
  readonly columnName: string;
310
310
  readonly columnType: Type;
311
+ readonly columnMultiplicity: Multiplicity;
311
312
 
312
- constructor(columnName: string, columnType: Type) {
313
+ constructor(
314
+ columnName: string,
315
+ columnType: Type,
316
+ columnMultiplicity: Multiplicity,
317
+ ) {
313
318
  super();
314
319
  this.columnName = columnName;
315
320
  this.columnType = columnType;
321
+ this.columnMultiplicity = columnMultiplicity;
322
+ }
323
+
324
+ get multiplicity(): Multiplicity {
325
+ return this.columnMultiplicity;
316
326
  }
317
327
 
318
328
  get type(): Type {
@@ -361,6 +371,8 @@ export class FilterRelationColumnSourceState extends FilterConditionSourceState
361
371
  return hashArray([
362
372
  QUERY_BUILDER_STATE_HASH_STRUCTURE.FILTER_CONDITION_SOURCE_RELATION_COLUMN,
363
373
  this.columnName,
374
+ this.columnMultiplicity.lowerBound,
375
+ this.columnMultiplicity.upperBound ?? '',
364
376
  ]);
365
377
  }
366
378
  }
@@ -331,10 +331,31 @@ const processFilterTree = (
331
331
  filterConditionState.sourceState instanceof
332
332
  FilterRelationColumnSourceState
333
333
  ) {
334
- const leftSide = expression.parametersValues[0];
334
+ // For a relation-column source the raw expression can be one of:
335
+ // - `isEmpty(col($x))` (IsEmpty / other unary ops)
336
+ // - `not(isEmpty(col($x)))` (IsNotEmpty)
337
+ // - `op(col($x), value)` (binary ops like equal, lessThan, ...)
338
+ // Walk down through any `not(...)` and any wrapping function call
339
+ // until we find the relation-column accessor `col($x)`, then read
340
+ // its variable parameter.
341
+ let current: ValueSpecification | undefined = expression;
342
+ while (
343
+ current instanceof SimpleFunctionExpression &&
344
+ !(
345
+ current.parametersValues[0] instanceof FunctionExpression &&
346
+ current.parametersValues[0].parametersValues[0] instanceof
347
+ VariableExpression
348
+ )
349
+ ) {
350
+ current = current.parametersValues[0];
351
+ }
352
+ const columnAccessor =
353
+ current instanceof SimpleFunctionExpression
354
+ ? current.parametersValues[0]
355
+ : current;
335
356
  const varExpr = guaranteeType(
336
- leftSide instanceof FunctionExpression
337
- ? leftSide.parametersValues[0]
357
+ columnAccessor instanceof FunctionExpression
358
+ ? columnAccessor.parametersValues[0]
338
359
  : undefined,
339
360
  VariableExpression,
340
361
  `Can't process filter expression: relation column filter must reference a variable`,
@@ -142,6 +142,7 @@ export const buildFilterConditionState = (
142
142
  new FilterRelationColumnSourceState(
143
143
  col.name,
144
144
  col.genericType.value.rawType,
145
+ col.multiplicity,
145
146
  ),
146
147
  );
147
148
  } else {
@@ -18,6 +18,7 @@ import {
18
18
  type QueryBuilderFilterState,
19
19
  type FilterConditionState,
20
20
  FilterPropertyExpressionSourceState,
21
+ FilterRelationColumnSourceState,
21
22
  } from '../QueryBuilderFilterState.js';
22
23
  import { QueryBuilderFilterOperator } from '../QueryBuilderFilterOperator.js';
23
24
  import {
@@ -52,16 +53,21 @@ export class QueryBuilderFilterOperator_IsEmpty
52
53
  filterConditionState: FilterConditionState,
53
54
  ): boolean {
54
55
  const propertyType = filterConditionState.leftConditionType;
55
- // First check if property is optional
56
- if (
57
- !(
58
- filterConditionState.sourceState instanceof
59
- FilterPropertyExpressionSourceState
60
- ) ||
61
- !isPropertyExpressionChainOptional(
62
- filterConditionState.propertyExpressionState.propertyExpression,
63
- )
64
- ) {
56
+ const sourceState = filterConditionState.sourceState;
57
+ // First check if source is optional (multiplicity lower bound === 0)
58
+ if (sourceState instanceof FilterPropertyExpressionSourceState) {
59
+ if (
60
+ !isPropertyExpressionChainOptional(
61
+ filterConditionState.propertyExpressionState.propertyExpression,
62
+ )
63
+ ) {
64
+ return false;
65
+ }
66
+ } else if (sourceState instanceof FilterRelationColumnSourceState) {
67
+ if (sourceState.columnMultiplicity.lowerBound !== 0) {
68
+ return false;
69
+ }
70
+ } else {
65
71
  return false;
66
72
  }
67
73
  return (