Skip to content

Commit

Permalink
Add interface aggregations on top level
Browse files Browse the repository at this point in the history
  • Loading branch information
angrykoala committed Jan 21, 2025
1 parent 6cc7a09 commit 60e7337
Show file tree
Hide file tree
Showing 9 changed files with 504 additions and 69 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export class ConnectionReadOperation extends Operation {
public nodeFields: Field[] = [];
public edgeFields: Field[] = []; // TODO: merge with attachedTo?

public aggregationField: ConnectionAggregationField | undefined;
public aggregationField: ConnectionAggregationField | undefined; // TODO: multiple aggregations

public filters: Filter[] = [];
protected pagination: Pagination | undefined;
Expand Down Expand Up @@ -130,6 +130,33 @@ export class ConnectionReadOperation extends Operation {
);
}

protected transpileAggregation(context: QueryASTContext): {
subqueries: Cypher.Clause[];
fields: Array<[Cypher.Expr, Cypher.Variable]>;
returnMap: Record<string, Cypher.Variable>;
} {
const returnMap: Record<string, Cypher.Variable> = {};
let extraFieldsSubqueries: Cypher.Clause[] = [];
let extraFields: Array<[Cypher.Expr, Cypher.Variable]> = [];
if (this.aggregationField) {
extraFieldsSubqueries = this.aggregationField.getSubqueries(context);

const aggregationProjectionField = this.aggregationField.getProjectionField();

extraFields = Object.entries(aggregationProjectionField).map(([key, value]) => {
const variable = new Cypher.Variable();
returnMap[key] = variable;
return [value, variable];
});
}

return {
fields: extraFields,
subqueries: extraFieldsSubqueries,
returnMap,
};
}

public transpile(context: QueryASTContext): OperationTranspileResult {
if (!context.target) throw new Error();

Expand All @@ -155,20 +182,11 @@ export class ConnectionReadOperation extends Operation {

const filtersSubqueries = [...authFilterSubqueries, ...normalFilterSubqueries];

const returnMap: Record<string, Cypher.Variable> = {};
let extraFieldsSubqueries: Cypher.Clause[] = [];
let extraFields: Array<[Cypher.Expr, Cypher.Variable]> = [];
if (this.aggregationField) {
extraFieldsSubqueries = this.aggregationField.getSubqueries(nestedContext);

const aggregationProjectionField = this.aggregationField.getProjectionField();

extraFields = Object.entries(aggregationProjectionField).map(([key, value]) => {
const variable = new Cypher.Variable();
returnMap[key] = variable;
return [value, variable];
});
}
const {
returnMap,
fields: extraFields,
subqueries: extraFieldsSubqueries,
} = this.transpileAggregation(nestedContext);

const edgesVar = new Cypher.NamedVariable("edges");
const totalCount = new Cypher.NamedVariable("totalCount");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ import { ConnectionReadOperation } from "../ConnectionReadOperation";
import type { OperationTranspileResult } from "../operations";

export class CompositeConnectionPartial extends ConnectionReadOperation {
/** Prints the name of the Node */
public print(): string {
return `${super.print()} <${this.target.name}>`;
}

public transpile(context: QueryASTContext): OperationTranspileResult {
// eslint-disable-next-line prefer-const
let { selection: clause, nestedContext } = this.selection.apply(context);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { filterTruthy } from "../../../../../utils/utils";
import { hasTarget } from "../../../utils/context-has-target";
import { QueryASTContext } from "../../QueryASTContext";
import type { QueryASTNode } from "../../QueryASTNode";
import type { ConnectionAggregationField } from "../../fields/ConnectionAggregationField";
import type { Pagination } from "../../pagination/Pagination";
import type { Sort, SortField } from "../../sort/Sort";
import type { CompositeConnectionPartial } from "./CompositeConnectionPartial";
Expand All @@ -34,11 +35,41 @@ export class CompositeConnectionReadOperation extends Operation {
protected sortFields: Array<{ node: Sort[]; edge: Sort[] }> = [];
private pagination: Pagination | undefined;

public aggregationField: ConnectionAggregationField | undefined; // TODO: multiple aggregations

constructor(children: CompositeConnectionPartial[]) {
super();
this.children = children;
}

// NOTE: duplicate from ConnectionReadOperation
protected transpileAggregation(context: QueryASTContext): {
subqueries: Cypher.Clause[];
fields: Array<[Cypher.Expr, Cypher.Variable]>;
returnMap: Record<string, Cypher.Variable>;
} {
const returnMap: Record<string, Cypher.Variable> = {};
let extraFieldsSubqueries: Cypher.Clause[] = [];
let extraFields: Array<[Cypher.Expr, Cypher.Variable]> = [];
if (this.aggregationField) {
extraFieldsSubqueries = this.aggregationField.getSubqueries(context);

const aggregationProjectionField = this.aggregationField.getProjectionField();

extraFields = Object.entries(aggregationProjectionField).map(([key, value]) => {
const variable = new Cypher.Variable();
returnMap[key] = variable;
return [value, variable];
});
}

return {
fields: extraFields,
subqueries: extraFieldsSubqueries,
returnMap,
};
}

public transpile(context: QueryASTContext): OperationTranspileResult {
const edgeVar = new Cypher.NamedVariable("edge");
const edgesVar = new Cypher.NamedVariable("edges");
Expand Down Expand Up @@ -94,18 +125,31 @@ export class CompositeConnectionReadOperation extends Operation {
orderSubquery = new Cypher.Call(extraWithOrder).importWith(edgesVar);
}

nestedSubquery.with([Cypher.collect(edgeVar), edgesVar]).with(edgesVar, [Cypher.size(edgesVar), totalCount]);
const {
fields: extraFields,
subqueries: aggregateSubqueries,
returnMap: aggregateReturnMap,
} = this.transpileAggregation(context);

const subqueryWith = new Cypher.With([Cypher.collect(edgeVar), edgesVar], ...extraFields).with(
edgesVar,
[Cypher.size(edgesVar), totalCount],
...(extraFields ?? []).map((c) => c[1])
);

const returnClause = new Cypher.Return([
new Cypher.Map({
edges: returnEdgesVar,
totalCount: totalCount,
...aggregateReturnMap,
}),
context.returnVariable,
]);

return {
clauses: [Cypher.utils.concat(nestedSubquery, orderSubquery, returnClause)],
clauses: [
Cypher.utils.concat(nestedSubquery, ...aggregateSubqueries, subqueryWith, orderSubquery, returnClause),
],
projectionExpr: context.returnVariable,
};
}
Expand All @@ -123,7 +167,7 @@ export class CompositeConnectionReadOperation extends Operation {
return [...s.edge, ...s.node];
});

return filterTruthy([...this.children, ...sortFields, this.pagination]);
return filterTruthy([...this.children, this.aggregationField, ...sortFields, this.pagination]);
}

protected getSortFields(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,6 @@ export class AggregateFactory {
operation.setNodeFields(nodeFields);
operation.setEdgeFields(edgeFields);
} else {
console.log("HERE");
const rawProjectionFields = {
...resolveTree.fieldsByTypeName[entity.operations.aggregateTypeNames.selection],
...resolveTree.fieldsByTypeName[entity.operations.aggregateTypeNames.node], // Handles both, deprecated and new aggregation parsing
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ export class ConnectionFactory {
const concreteConnectionOperations = concreteEntities.map((concreteEntity: ConcreteEntityAdapter) => {
let selection: EntitySelection;
let resolveTreeEdgeFields: Record<string, ResolveTree>;

if (relationship) {
selection = new RelationshipSelection({
relationship,
Expand Down Expand Up @@ -130,6 +131,43 @@ export class ConnectionFactory {

const compositeConnectionOp = new CompositeConnectionReadOperation(concreteConnectionOperations);

if (isInterfaceEntity(target)) {
const fields = resolveTree.fieldsByTypeName[target.operations.connectionFieldTypename];
if (fields) {
const resolveTreeAggregate = findFieldsByNameInFieldsByTypeNameField(fields, "aggregate")[0];
if (resolveTreeAggregate) {
const resolveTreeAggregateFields =
resolveTreeAggregate.fieldsByTypeName[target.operations.aggregateTypeNames.connection];

if (resolveTreeAggregateFields) {
// Maybe this should be using operationFactory instead
const nodeField = getResolveTreeByFieldName({
fieldName: "node",
selection: resolveTreeAggregateFields,
});

if (nodeField) {
const typeNameNode = target.operations.aggregateTypeNames.node;
// Sugar syntax for aggregations. This allows to use normal aggregations in each composite field, by renaming the type name
nodeField.fieldsByTypeName[typeNameNode] =
nodeField.fieldsByTypeName[target.operations.aggregateTypeNames.node] ?? {};
const aggregationOperation = this.aggregateFactory.createAggregationOperation({
entityOrRel: relationship ?? target,
resolveTree: nodeField,
context,
});
const aggregationField = new ConnectionAggregationField({
alias: resolveTreeAggregate.name, // Alias is hanlded by graphql on top level
nodeAlias: nodeField.alias,
operation: aggregationOperation,
});
compositeConnectionOp.aggregationField = aggregationField;
}
}
}
}
}

// These sort fields will be duplicated on nested "CompositeConnectionPartial"

this.hydrateConnectionOperationsASTWithSort({
Expand Down
Loading

0 comments on commit 60e7337

Please sign in to comment.