@finos/legend-query-builder 4.18.16 → 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.
- package/lib/components/explorer/QueryBuilderRelationExplorerPanel.d.ts.map +1 -1
- package/lib/components/explorer/QueryBuilderRelationExplorerPanel.js +14 -2
- package/lib/components/explorer/QueryBuilderRelationExplorerPanel.js.map +1 -1
- package/lib/components/filter/QueryBuilderFilterPanel.d.ts.map +1 -1
- package/lib/components/filter/QueryBuilderFilterPanel.js +23 -3
- package/lib/components/filter/QueryBuilderFilterPanel.js.map +1 -1
- package/lib/components/sql-playground/SQLPlaygroundGrid.d.ts.map +1 -1
- package/lib/components/sql-playground/SQLPlaygroundGrid.js +4 -1
- package/lib/components/sql-playground/SQLPlaygroundGrid.js.map +1 -1
- package/lib/data-access-overview.css +1 -1
- package/lib/index.css +2 -2
- package/lib/index.css.map +1 -1
- package/lib/package.json +1 -1
- package/lib/stores/fetch-structure/tds/aggregation/QueryBuilderRelationAggregationValueSpecBuilder.d.ts.map +1 -1
- package/lib/stores/fetch-structure/tds/aggregation/QueryBuilderRelationAggregationValueSpecBuilder.js +44 -12
- package/lib/stores/fetch-structure/tds/aggregation/QueryBuilderRelationAggregationValueSpecBuilder.js.map +1 -1
- package/lib/stores/fetch-structure/tds/post-filter/operators/QueryBuilderPostFilterOperatorValueSpecificationBuilder.d.ts.map +1 -1
- package/lib/stores/fetch-structure/tds/post-filter/operators/QueryBuilderPostFilterOperatorValueSpecificationBuilder.js +12 -0
- package/lib/stores/fetch-structure/tds/post-filter/operators/QueryBuilderPostFilterOperatorValueSpecificationBuilder.js.map +1 -1
- package/lib/stores/fetch-structure/tds/post-filter/operators/QueryBuilderPostFilterOperator_IsEmpty.d.ts +4 -2
- package/lib/stores/fetch-structure/tds/post-filter/operators/QueryBuilderPostFilterOperator_IsEmpty.d.ts.map +1 -1
- package/lib/stores/fetch-structure/tds/post-filter/operators/QueryBuilderPostFilterOperator_IsEmpty.js +50 -6
- package/lib/stores/fetch-structure/tds/post-filter/operators/QueryBuilderPostFilterOperator_IsEmpty.js.map +1 -1
- package/lib/stores/fetch-structure/tds/projection/QueryBuilderProjectionValueSpecificationBuilder.js +1 -1
- package/lib/stores/fetch-structure/tds/projection/QueryBuilderProjectionValueSpecificationBuilder.js.map +1 -1
- package/lib/stores/filter/QueryBuilderFilterState.d.ts +4 -2
- package/lib/stores/filter/QueryBuilderFilterState.d.ts.map +1 -1
- package/lib/stores/filter/QueryBuilderFilterState.js +8 -1
- 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 +19 -3
- package/lib/stores/filter/QueryBuilderFilterStateBuilder.js.map +1 -1
- package/lib/stores/filter/operators/QueryBuilderFilterOperatorValueSpecificationBuilder.d.ts.map +1 -1
- package/lib/stores/filter/operators/QueryBuilderFilterOperatorValueSpecificationBuilder.js +1 -1
- package/lib/stores/filter/operators/QueryBuilderFilterOperatorValueSpecificationBuilder.js.map +1 -1
- package/lib/stores/filter/operators/QueryBuilderFilterOperator_IsEmpty.d.ts.map +1 -1
- package/lib/stores/filter/operators/QueryBuilderFilterOperator_IsEmpty.js +14 -5
- package/lib/stores/filter/operators/QueryBuilderFilterOperator_IsEmpty.js.map +1 -1
- package/package.json +8 -8
- package/src/components/explorer/QueryBuilderRelationExplorerPanel.tsx +22 -0
- package/src/components/filter/QueryBuilderFilterPanel.tsx +46 -0
- package/src/components/sql-playground/SQLPlaygroundGrid.tsx +15 -2
- package/src/stores/fetch-structure/tds/aggregation/QueryBuilderRelationAggregationValueSpecBuilder.ts +82 -29
- package/src/stores/fetch-structure/tds/post-filter/operators/QueryBuilderPostFilterOperatorValueSpecificationBuilder.ts +13 -0
- package/src/stores/fetch-structure/tds/post-filter/operators/QueryBuilderPostFilterOperator_IsEmpty.ts +91 -6
- package/src/stores/fetch-structure/tds/projection/QueryBuilderProjectionValueSpecificationBuilder.ts +1 -1
- package/src/stores/filter/QueryBuilderFilterState.ts +13 -1
- package/src/stores/filter/QueryBuilderFilterStateBuilder.ts +24 -3
- package/src/stores/filter/operators/QueryBuilderFilterOperatorValueSpecificationBuilder.ts +1 -0
- package/src/stores/filter/operators/QueryBuilderFilterOperator_IsEmpty.ts +16 -10
|
@@ -32,6 +32,7 @@ import {
|
|
|
32
32
|
PropertyExplicitReference,
|
|
33
33
|
Property,
|
|
34
34
|
VariableExpression,
|
|
35
|
+
FunctionExpression,
|
|
35
36
|
} from '@finos/legend-graph';
|
|
36
37
|
import { QUERY_BUILDER_SUPPORTED_FUNCTIONS } from '../../../../graph/QueryBuilderMetaModelConst.js';
|
|
37
38
|
import type { QueryBuilderTDSState } from '../QueryBuilderTDSState.js';
|
|
@@ -42,6 +43,11 @@ import {
|
|
|
42
43
|
UnsupportedOperationError,
|
|
43
44
|
} from '@finos/legend-shared';
|
|
44
45
|
import { DEFAULT_LAMBDA_VARIABLE_NAME } from '@finos/legend-data-cube';
|
|
46
|
+
import {
|
|
47
|
+
QueryBuilderDerivationProjectionColumnState,
|
|
48
|
+
QueryBuilderRelationColumnProjectionColumnState,
|
|
49
|
+
QueryBuilderSimpleProjectionColumnState,
|
|
50
|
+
} from '../projection/QueryBuilderProjectionColumnState.js';
|
|
45
51
|
|
|
46
52
|
export const buildRelationAggregation = (
|
|
47
53
|
precedingExpression: ValueSpecification,
|
|
@@ -131,33 +137,84 @@ export const buildRelationAggregation = (
|
|
|
131
137
|
),
|
|
132
138
|
`Could not find projected column matching aggregation column '${aggregationColumnState.columnName}'`,
|
|
133
139
|
);
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
const projectedProperty = guaranteeType(
|
|
140
|
-
projectedPropertyExpression.func.value,
|
|
141
|
-
Property,
|
|
140
|
+
// Add column return type to relationType (computed up-front so we can also
|
|
141
|
+
// reuse it when building the map lambda for relation/derivation columns).
|
|
142
|
+
const returnType = guaranteeNonNullable(
|
|
143
|
+
aggregationColumnState.getColumnType(),
|
|
144
|
+
`Can't create value spec for aggregation column ${aggregationColumnState.columnName}. Missing type.`,
|
|
142
145
|
);
|
|
143
|
-
// Second, build
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
146
|
+
// Second, build the map lambda. The shape of the lambda depends on the
|
|
147
|
+
// kind of projection column being aggregated:
|
|
148
|
+
// - Simple (class-property) projections: rebuild an AbstractPropertyExpression
|
|
149
|
+
// referencing the projected column name.
|
|
150
|
+
// - Relation-column projections (source is an Accessor/Relation): build a
|
|
151
|
+
// FunctionExpression over a RelationColumn with the projected name.
|
|
152
|
+
// - Derivation projections: reuse the lambda already produced by project().
|
|
153
|
+
let mapLambda: ValueSpecification;
|
|
154
|
+
const projectionColumnState = aggregationColumnState.projectionColumnState;
|
|
155
|
+
if (
|
|
156
|
+
projectionColumnState instanceof QueryBuilderSimpleProjectionColumnState
|
|
157
|
+
) {
|
|
158
|
+
const projectedPropertyExpression = guaranteeType(
|
|
159
|
+
guaranteeType(projectionColSpec.function1, LambdaFunctionInstanceValue)
|
|
160
|
+
.values[0]?.expressionSequence[0],
|
|
161
|
+
AbstractPropertyExpression,
|
|
162
|
+
);
|
|
163
|
+
const projectedProperty = guaranteeType(
|
|
164
|
+
projectedPropertyExpression.func.value,
|
|
165
|
+
Property,
|
|
166
|
+
);
|
|
167
|
+
const newPropertyExpression = new AbstractPropertyExpression('');
|
|
168
|
+
newPropertyExpression.func = PropertyExplicitReference.create(
|
|
169
|
+
new Property(
|
|
170
|
+
projectionColSpec.name,
|
|
171
|
+
projectedProperty.multiplicity,
|
|
172
|
+
projectedProperty.genericType,
|
|
173
|
+
projectedProperty._OWNER,
|
|
174
|
+
),
|
|
175
|
+
);
|
|
176
|
+
newPropertyExpression.parametersValues = [
|
|
177
|
+
new VariableExpression(DEFAULT_LAMBDA_VARIABLE_NAME, Multiplicity.ONE),
|
|
178
|
+
];
|
|
179
|
+
mapLambda = buildGenericLambdaFunctionInstanceValue(
|
|
180
|
+
[DEFAULT_LAMBDA_VARIABLE_NAME],
|
|
181
|
+
[newPropertyExpression],
|
|
182
|
+
queryBuilderState.graphManagerState.graph,
|
|
183
|
+
);
|
|
184
|
+
} else if (
|
|
185
|
+
projectionColumnState instanceof
|
|
186
|
+
QueryBuilderRelationColumnProjectionColumnState
|
|
187
|
+
) {
|
|
188
|
+
const projectedColumn = new RelationColumn(
|
|
147
189
|
projectionColSpec.name,
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
190
|
+
GenericTypeExplicitReference.create(new GenericType(returnType)),
|
|
191
|
+
);
|
|
192
|
+
const columnExpression = new FunctionExpression(projectionColSpec.name);
|
|
193
|
+
columnExpression.func = projectedColumn;
|
|
194
|
+
columnExpression.parametersValues = [
|
|
195
|
+
new VariableExpression(DEFAULT_LAMBDA_VARIABLE_NAME, Multiplicity.ONE),
|
|
196
|
+
];
|
|
197
|
+
mapLambda = buildGenericLambdaFunctionInstanceValue(
|
|
198
|
+
[DEFAULT_LAMBDA_VARIABLE_NAME],
|
|
199
|
+
[columnExpression],
|
|
200
|
+
queryBuilderState.graphManagerState.graph,
|
|
201
|
+
);
|
|
202
|
+
} else if (
|
|
203
|
+
projectionColumnState instanceof
|
|
204
|
+
QueryBuilderDerivationProjectionColumnState
|
|
205
|
+
) {
|
|
206
|
+
// The derivation lambda was already serialized as part of project();
|
|
207
|
+
// reuse it directly so we don't need to re-serialize it here.
|
|
208
|
+
mapLambda = guaranteeNonNullable(
|
|
209
|
+
projectionColSpec.function1,
|
|
210
|
+
`Could not find projected derivation lambda for aggregation column '${aggregationColumnState.columnName}'`,
|
|
211
|
+
);
|
|
212
|
+
} else {
|
|
213
|
+
throw new UnsupportedOperationError(
|
|
214
|
+
`Can't build relation groupBy() aggregation map function: unsupported projection column state`,
|
|
215
|
+
projectionColumnState,
|
|
216
|
+
);
|
|
217
|
+
}
|
|
161
218
|
colSpec.function1 = mapLambda;
|
|
162
219
|
|
|
163
220
|
// Reduce function (function2)
|
|
@@ -174,10 +231,6 @@ export const buildRelationAggregation = (
|
|
|
174
231
|
colSpec.function2 = reduceLambda;
|
|
175
232
|
|
|
176
233
|
// Add column return type to relationType
|
|
177
|
-
const returnType = guaranteeNonNullable(
|
|
178
|
-
aggregationColumnState.getColumnType(),
|
|
179
|
-
`Can't create value spec for aggregation column ${aggregationColumnState.columnName}. Missing type.`,
|
|
180
|
-
);
|
|
181
234
|
relationType.columns.push(
|
|
182
235
|
new RelationColumn(
|
|
183
236
|
aggregationColumnState.columnName,
|
|
@@ -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
|
-
|
|
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 {
|
|
35
|
+
import {
|
|
36
|
+
QueryBuilderRelationColumnProjectionColumnState,
|
|
37
|
+
QueryBuilderSimpleProjectionColumnState,
|
|
38
|
+
} from '../../projection/QueryBuilderProjectionColumnState.js';
|
|
34
39
|
import { buildPostFilterConditionExpressionHelper } from './QueryBuilderPostFilterOperatorValueSpecificationBuilder.js';
|
|
35
|
-
import {
|
|
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 {
|
|
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
|
-
|
|
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,
|
package/src/stores/fetch-structure/tds/projection/QueryBuilderProjectionValueSpecificationBuilder.ts
CHANGED
|
@@ -285,7 +285,7 @@ export const appendProjection = (
|
|
|
285
285
|
// build projection
|
|
286
286
|
if (
|
|
287
287
|
tdsState.aggregationState.columns.length &&
|
|
288
|
-
!tdsState.queryBuilderState.
|
|
288
|
+
!tdsState.queryBuilderState.useRelation
|
|
289
289
|
) {
|
|
290
290
|
// aggregation
|
|
291
291
|
const groupByFunction = new SimpleFunctionExpression(
|
|
@@ -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(
|
|
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
|
-
|
|
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
|
-
|
|
337
|
-
?
|
|
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`,
|
|
@@ -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
|
-
|
|
56
|
-
if (
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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 (
|