Skip to content

Commit

Permalink
feat: Support converting TDSv2 groupBy protocol to state and fix hand…
Browse files Browse the repository at this point in the history
…ling nested properties
  • Loading branch information
travisstebbins committed Feb 12, 2025
1 parent 8a5d954 commit b7fdc74
Show file tree
Hide file tree
Showing 4 changed files with 160 additions and 108 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1224,7 +1224,6 @@ export const V1_buildTypedGroupByFunctionExpression = (
compileContext: V1_GraphBuilderContext,
processingContext: V1_ProcessingContext,
): SimpleFunctionExpression => {
console.log('V1_buildTypedGroupByFunctionExpression');
let topLevelLambdaParameters: V1_Variable[] = [];

assertTrue(
Expand Down Expand Up @@ -1332,8 +1331,6 @@ export const V1_buildTypedGroupByFunctionExpression = (
processedAggregationColSpecArray.colSpecs =
aggregationExpressions.colSpecs.map((colSpec) => {
const pColSpec = new ColSpec();
let lambda1: ValueSpecification;
let lambda2: ValueSpecification;
const _func1 = guaranteeType(
colSpec.function1,
V1_ValueSpecification,
Expand All @@ -1344,41 +1341,23 @@ export const V1_buildTypedGroupByFunctionExpression = (
V1_ValueSpecification,
`Can't build relation col spec() expression: expects function2 to be a lambda`,
);
try {
lambda1 = buildProjectionColumnLambda(
_func1,
openVariables,
compileContext,
processingContext,
);
} catch {
lambda1 = new INTERNAL__UnknownValueSpecification(
V1_serializeValueSpecification(
_func1,
compileContext.extensions.plugins,
),
);
}
const lambda1: ValueSpecification = buildProjectionColumnLambda(
_func1,
openVariables,
compileContext,
processingContext,
);
pColSpec.function1 = lambda1;
try {
lambda2 = _func2.accept_ValueSpecificationVisitor(
const lambda2: ValueSpecification =
_func2.accept_ValueSpecificationVisitor(
new V1_ValueSpecificationBuilder(
compileContext,
processingContext,
openVariables,
),
);
} catch {
lambda2 = new INTERNAL__UnknownValueSpecification(
V1_serializeValueSpecification(
_func1,
compileContext.extensions.plugins,
),
);
}
pColSpec.function2 = lambda2;
pColSpec.name = colSpec.name;
console.log('lambda2', lambda2);
const relationColumns = guaranteeType(
guaranteeType(lambda2, LambdaFunctionInstanceValue).values?.[0]
?.functionType?.parameters?.[0]?.genericType?.value.typeArguments?.[0]
Expand All @@ -1390,13 +1369,6 @@ export const V1_buildTypedGroupByFunctionExpression = (
return pColSpec;
});

console.log('precedingExpression', precedingExpression);
console.log('processedColumnExpressions', processedColumnExpressions);
console.log(
'processedAggregationExpressions',
processedAggregationExpressions,
);

const expression = V1_buildBaseSimpleFunctionExpression(
[
precedingExpression,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ import {
} from './fetch-structure/tds/projection/QueryBuilderTypedProjectionStateBuilder.js';
import {
isTypedGroupByExpression,
processTypedAggregationColSpec,
processTypedGroupByExpression,
} from './fetch-structure/tds/aggregation/QueryBuilderTypedAggregationStateBuilder.js';

Expand Down Expand Up @@ -994,7 +995,6 @@ export class QueryBuilderValueSpecificationProcessor
lambdaVal.expressionSequence[0],
);

// TODO: Handle groupBy col spec (has function1 and function2)
if (expression instanceof AbstractPropertyExpression) {
processTDSProjectionColumnPropertyExpression(
expression,
Expand Down Expand Up @@ -1027,47 +1027,11 @@ export class QueryBuilderValueSpecificationProcessor
`Can't process col spec array instance: value expected to be of size 1`,
);
guaranteeNonNullable(spec[0]).colSpecs.forEach((col) => {
const _function1 = guaranteeType(
col.function1,
LambdaFunctionInstanceValue,
`Can't process col spec: function1 not a lambda function instance value`,
processTypedAggregationColSpec(
col,
this.parentExpression,
this.queryBuilderState,
);
assertTrue(_function1.values.length === 1);
const lambdaVal = guaranteeNonNullable(_function1.values[0]);
assertTrue(lambdaVal.expressionSequence.length === 1);
// const expression = guaranteeNonNullable(
// lambdaVal.expressionSequence[0],
// );

// if (expression instanceof AbstractPropertyExpression) {
// processTDSProjectionColumnPropertyExpression(
// expression,
// col.name,
// this.queryBuilderState,
// );
// } else if (expression instanceof INTERNAL__UnknownValueSpecification) {
// assertNonNullable(
// this.parentExpression,
// `Can't process unknown value: parent expression cannot be retrieved`,
// );
// processTDSProjectionDerivationExpression(
// expression,
// col.name,
// this.parentExpression,
// this.queryBuilderState,
// );
// }

if (col.function2) {
const _function2 = guaranteeType(
col.function2,
LambdaFunctionInstanceValue,
`Can't process col spec: function2 not a lambda function instance value`,
);
assertTrue(_function2.values.length === 1);
const lambdaVal2 = guaranteeNonNullable(_function2.values[0]);
assertTrue(lambdaVal2.expressionSequence.length === 1);
}
});

return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
AbstractPropertyExpression,
PropertyExplicitReference,
Property,
VariableExpression,
} from '@finos/legend-graph';
import { QUERY_BUILDER_SUPPORTED_FUNCTIONS } from '../../../../graph/QueryBuilderMetaModelConst.js';
import type { QueryBuilderTDSState } from '../QueryBuilderTDSState.js';
Expand Down Expand Up @@ -141,7 +142,7 @@ export const buildRelationAggregation = (
),
);
newPropertyExpression.parametersValues = [
...projectedPropertyExpression.parametersValues,
new VariableExpression(DEFAULT_LAMBDA_VARIABLE_NAME, Multiplicity.ONE),
];
const columnLambda = buildGenericLambdaFunctionInstanceValue(
DEFAULT_LAMBDA_VARIABLE_NAME,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,30 +15,146 @@
*/

import {
type ColSpec,
type LambdaFunction,
SimpleFunctionExpression,
matchFunctionName,
ColSpecArrayInstance,
LambdaFunctionInstanceValue,
matchFunctionName,
SimpleFunctionExpression,
VariableExpression,
} from '@finos/legend-graph';
import { FETCH_STRUCTURE_IMPLEMENTATION } from '../../QueryBuilderFetchStructureImplementationState.js';
import { assertTrue, assertType, guaranteeType } from '@finos/legend-shared';
import {
assertTrue,
assertType,
guaranteeNonNullable,
guaranteeType,
returnUndefOnError,
UnsupportedOperationError,
} from '@finos/legend-shared';
import {
QUERY_BUILDER_LAMBDA_WRITER_MODE,
type QueryBuilderState,
} from '../../../QueryBuilderState.js';
import { QUERY_BUILDER_SUPPORTED_FUNCTIONS } from '../../../../graph/QueryBuilderMetaModelConst.js';
import { QueryBuilderValueSpecificationProcessor } from '../../../QueryBuilderStateBuilder.js';
import { QueryBuilderTDSState } from '../QueryBuilderTDSState.js';
import { QueryBuilderAggregateOperator_Wavg } from './operators/QueryBuilderAggregateOperator_Wavg.js';

export const processTypedAggregationColSpec = (
colSpec: ColSpec,
parentExpression: SimpleFunctionExpression | undefined,
queryBuilderState: QueryBuilderState,
): void => {
// check parent expression
assertTrue(
Boolean(
parentExpression &&
matchFunctionName(
parentExpression.functionName,
QUERY_BUILDER_SUPPORTED_FUNCTIONS.RELATION_GROUP_BY,
),
),
`Can't process typed aggregate ColSpec: only supported when used within a groupBy() expression`,
);

// Check that there are 2 lambdas, one for column selection and 1 for aggregation
const columnLambdaFuncInstance = guaranteeType(
colSpec.function1,
LambdaFunctionInstanceValue,
`Can't process colSpec: function1 not a lambda function instance value`,
);
assertTrue(columnLambdaFuncInstance.values.length === 1);
assertTrue(
guaranteeNonNullable(columnLambdaFuncInstance.values[0]).expressionSequence
.length === 1,
);

const aggregationLambdaFuncInstance = guaranteeType(
colSpec.function2,
LambdaFunctionInstanceValue,
`Can't process colSpec: function2 not a lambda function instance value`,
);
assertTrue(aggregationLambdaFuncInstance.values.length === 1);
assertTrue(
guaranteeNonNullable(aggregationLambdaFuncInstance.values[0])
.expressionSequence.length === 1,
);

// build state
if (
queryBuilderState.fetchStructureState.implementation instanceof
QueryBuilderTDSState
) {
const tdsState = queryBuilderState.fetchStructureState.implementation;
const aggregationState = tdsState.aggregationState;
const projectionColumnState = guaranteeNonNullable(
tdsState.projectionColumns.find(
(projectionColumn) => projectionColumn.columnName === colSpec.name,
),
`Projection column with name ${colSpec.name} not found`,
);
const aggregateLambdaFunc = guaranteeNonNullable(
aggregationLambdaFuncInstance.values[0],
`Can't process colSpec: function2 lambda function is missing`,
);
assertTrue(
aggregateLambdaFunc.expressionSequence.length === 1,
`Can't process colSpec: only support colSpec function2 lambda body with 1 expression`,
);
const aggregateColumnExpression = guaranteeType(
aggregateLambdaFunc.expressionSequence[0],
SimpleFunctionExpression,
`Can't process colSpec: only support colSpec function2 lambda body with 1 expression`,
);

assertTrue(
aggregateLambdaFunc.functionType.parameters.length === 1,
`Can't process colSpec function2 lambda: only support lambda with 1 parameter`,
);

const lambdaParam = guaranteeType(
aggregateLambdaFunc.functionType.parameters[0],
VariableExpression,
`Can't process colSpec function2 lambda: only support lambda with 1 parameter`,
);

for (const operator of aggregationState.operators) {
// NOTE: this allow plugin author to either return `undefined` or throw error
// if there is a problem with building the lambda. Either case, the plugin is
// considered as not supporting the lambda.
const aggregateColumnState = returnUndefOnError(() =>
operator.buildAggregateColumnState(
aggregateColumnExpression,
lambdaParam,
projectionColumnState,
),
);
if (
projectionColumnState.wavgWeight &&
aggregateColumnState &&
aggregateColumnState.operator instanceof
QueryBuilderAggregateOperator_Wavg
) {
aggregateColumnState.operator.setWeight(
projectionColumnState.wavgWeight,
);
}
if (aggregateColumnState) {
aggregationState.addColumn(aggregateColumnState);
return;
}
}
}
throw new UnsupportedOperationError(
`Can't process aggregate expression function: no compatible aggregate operator processer available from plugins`,
);
};

export const processTypedGroupByExpression = (
expression: SimpleFunctionExpression,
queryBuilderState: QueryBuilderState,
parentLambda: LambdaFunction,
): void => {
// update fetch-structureTABULAR_DATA_STRUCTURE
queryBuilderState.fetchStructureState.changeImplementation(
FETCH_STRUCTURE_IMPLEMENTATION.TABULAR_DATA_STRUCTURE,
);

// check parameters
assertTrue(
expression.parametersValues.length === 3,
Expand All @@ -65,22 +181,32 @@ export const processTypedGroupByExpression = (
queryBuilderState,
);

// process columns
const columnExpressions = expression.parametersValues[1];
assertType(
columnExpressions,
const tdsState = guaranteeType(
queryBuilderState.fetchStructureState.implementation,
QueryBuilderTDSState,
);

// process columns (ensure columns exist in project expression)
const columnExpressions = guaranteeType(
expression.parametersValues[1],
ColSpecArrayInstance,
`Can't process groupBy() expression: groupBy() expects argument #1 to be a ColSpecArrayInstance`,
);
assertTrue(
columnExpressions.values.length === 1,
`Can't process groupBy() expression: groupBy() expects argument #1 to be a ColSpecArrayInstance with 1 element`,
);
queryBuilderState.setLambdaWriteMode(
QUERY_BUILDER_LAMBDA_WRITER_MODE.TYPED_FETCH_STRUCTURE,
);
QueryBuilderValueSpecificationProcessor.processChild(
columnExpressions,
expression,
parentLambda,
queryBuilderState,
);
columnExpressions.values[0]?.colSpecs.forEach((colSpec) => {
assertTrue(
tdsState.projectionColumns.filter(
(projectedColumn) => projectedColumn.columnName === colSpec.name,
).length === 1,
`Can't process groupBy() expression: column '${colSpec.name}' not found in project() expression`,
);
});

// process aggregations
const aggregateLambdas = expression.parametersValues[2];
Expand All @@ -95,17 +221,6 @@ export const processTypedGroupByExpression = (
parentLambda,
queryBuilderState,
);

// build state
// if (
// queryBuilderState.fetchStructureState.implementation instanceof
// QueryBuilderTDSState
// ) {
// const tdsState = queryBuilderState.fetchStructureState.implementation;
// tdsState.projectionColumns.forEach((column, idx) =>
// column.setColumnName(aliases[idx] as string),
// );
// }
};

export const isTypedGroupByExpression = (
Expand Down

0 comments on commit b7fdc74

Please sign in to comment.