Skip to content

Commit

Permalink
feat: add CypherFilter
Browse files Browse the repository at this point in the history
  • Loading branch information
mjfwebb committed Sep 10, 2024
1 parent d393521 commit a1baf91
Show file tree
Hide file tree
Showing 2 changed files with 151 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/*
* Copyright (c) "Neo4j"
* Neo4j Sweden AB [http://neo4j.com]
*
* This file is part of Neo4j.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import Cypher from "@neo4j/cypher-builder";
import type { AttributeAdapter } from "../../../../../schema-model/attribute/model-adapters/AttributeAdapter";
import { createComparisonOperation } from "../../../utils/create-comparison-operator";
import { createPointOperation } from "../../../utils/create-point-operation";
import type { QueryASTContext } from "../../QueryASTContext";
import type { QueryASTNode } from "../../QueryASTNode";
import type { CustomCypherSelection } from "../../selection/CustomCypherSelection";
import type { FilterOperator } from "../Filter";
import { Filter } from "../Filter";

/** A property which comparison has already been parsed into a Param */
export class CypherFilter extends Filter {
private returnVariable: Cypher.Variable = new Cypher.Variable();
private attribute: AttributeAdapter;
private selection: CustomCypherSelection;
private operator: FilterOperator;
protected comparisonValue: unknown;

constructor({
selection,
attribute,
operator,
comparisonValue,
}: {
selection: CustomCypherSelection;
attribute: AttributeAdapter;
operator: FilterOperator;
comparisonValue: unknown;
}) {
super();
this.selection = selection;
this.attribute = attribute;
this.operator = operator;
this.comparisonValue = comparisonValue;
}

public getChildren(): QueryASTNode[] {
return [this.selection];
}

public print(): string {
return `${super.print()} [${this.attribute.name}] <${this.operator}>`;
}

public getSubqueries(context: QueryASTContext): Cypher.Clause[] {
const { selection: cypherSubquery, nestedContext } = this.selection.apply(context);

const clause = Cypher.concat(
cypherSubquery,
new Cypher.Return([nestedContext.returnVariable, this.returnVariable])
);

return [clause];
}

public getPredicate(_queryASTContext: QueryASTContext): Cypher.Predicate {
const operation = this.createBaseOperation({
operator: this.operator,
property: this.returnVariable,
param: new Cypher.Param(this.comparisonValue),
});

// const scope = context.getTargetScope();
// // by setting the return variable of this operation in the attribute scope, we can avoid duplicate the same cypher resolution for sorting and projection purposes
// scope.set(this.cypherAttributeField.name, context.returnVariable);

return operation;

// let retProj;

// if (this.isNested && this.cypherAttributeField.typeHelper.isList()) {
// retProj = [Cypher.collect(nestedContext.returnVariable), context.returnVariable];
// } else {
// retProj = [nestedContext.returnVariable, context.returnVariable];
// }
// const ret = new Cypher.Return(retProj);
}

private coalesceValueIfNeeded(expr: Cypher.Expr): Cypher.Expr {
if (this.attribute.annotations.coalesce) {
const value = this.attribute.annotations.coalesce.value;
const literal = new Cypher.Literal(value);
return Cypher.coalesce(expr, literal);
}
return expr;
}

/** Returns the default operation for a given filter */
private createBaseOperation({
operator,
property,
param,
}: {
operator: FilterOperator;
property: Cypher.Expr;
param: Cypher.Expr;
}): Cypher.ComparisonOp {
const coalesceProperty = this.coalesceValueIfNeeded(property);

// Check for point

if (operator === "DISTANCE") {
return createPointOperation({
operator,
property: coalesceProperty,
param: new Cypher.Param(this.comparisonValue),
attribute: this.attribute,
});
}

return createComparisonOperation({ operator, property: coalesceProperty, param });
}
}
20 changes: 19 additions & 1 deletion packages/graphql/src/translate/queryAST/factory/FilterFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,12 @@ import { AggregationDurationFilter } from "../ast/filters/aggregation/Aggregatio
import { AggregationFilter } from "../ast/filters/aggregation/AggregationFilter";
import { AggregationPropertyFilter } from "../ast/filters/aggregation/AggregationPropertyFilter";
import { CountFilter } from "../ast/filters/aggregation/CountFilter";
import { CypherFilter } from "../ast/filters/property-filters/CypherFilter";
import { DurationFilter } from "../ast/filters/property-filters/DurationFilter";
import { PointFilter } from "../ast/filters/property-filters/PointFilter";
import { PropertyFilter } from "../ast/filters/property-filters/PropertyFilter";
import { TypenameFilter } from "../ast/filters/property-filters/TypenameFilter";
import { CustomCypherSelection } from "../ast/selection/CustomCypherSelection";
import { getConcreteEntities } from "../utils/get-concrete-entities";
import { isConcreteEntity } from "../utils/is-concrete-entity";
import { isInterfaceEntity } from "../utils/is-interface-entity";
Expand Down Expand Up @@ -180,8 +182,24 @@ export class FilterFactory {
operator: WhereOperator | undefined;
isNot: boolean;
attachedTo?: "node" | "relationship";
}): PropertyFilter {
}): PropertyFilter | CypherFilter {
const filterOperator = operator || "EQ";

if (attribute.annotations.cypher) {
const selection = new CustomCypherSelection({
operationField: attribute,
rawArguments: {},
isNested: true,
});

return new CypherFilter({
selection,
attribute,
comparisonValue,
operator: filterOperator,
});
}

if (attribute.typeHelper.isDuration()) {
return new DurationFilter({
attribute,
Expand Down

0 comments on commit a1baf91

Please sign in to comment.