Skip to content

Commit

Permalink
Fix aliased fields case on interface relationship connection filters (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
a-alle authored Apr 3, 2024
1 parent 0027d5a commit cb83cf5
Show file tree
Hide file tree
Showing 7 changed files with 607 additions and 3 deletions.
5 changes: 5 additions & 0 deletions .changeset/sixty-grapes-rest.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@neo4j/graphql": patch
---

Fix aliased fields case on interface relationship connection filters
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import type { QueryASTContext } from "../../QueryASTContext";
import { PropertyFilter } from "./PropertyFilter";
import type { AttributeAdapter } from "../../../../../schema-model/attribute/model-adapters/AttributeAdapter";
import type { FilterOperator } from "../Filter";
import type { RelationshipAdapter } from "../../../../../schema-model/relationship/model-adapters/RelationshipAdapter";

type CypherVariable = Cypher.Variable | Cypher.Property | Cypher.Param;

Expand All @@ -35,6 +36,7 @@ export class ParamPropertyFilter extends PropertyFilter {
operator: FilterOperator;
isNot: boolean;
attachedTo?: "node" | "relationship";
relationship?: RelationshipAdapter;
}) {
super(options);
this.comparisonValue = options.comparisonValue;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,29 +25,35 @@ import type { QueryASTNode } from "../../QueryASTNode";
import type { FilterOperator } from "../Filter";
import { Filter } from "../Filter";
import { hasTarget } from "../../../utils/context-has-target";
import type { RelationshipAdapter } from "../../../../../schema-model/relationship/model-adapters/RelationshipAdapter";
import { InterfaceEntityAdapter } from "../../../../../schema-model/entity/model-adapters/InterfaceEntityAdapter";

export class PropertyFilter extends Filter {
protected attribute: AttributeAdapter;
protected relationship: RelationshipAdapter | undefined;
protected comparisonValue: unknown;
protected operator: FilterOperator;
protected isNot: boolean; // _NOT is deprecated
protected attachedTo: "node" | "relationship";

constructor({
attribute,
relationship,
comparisonValue,
operator,
isNot,
attachedTo,
}: {
attribute: AttributeAdapter;
relationship?: RelationshipAdapter;
comparisonValue: unknown;
operator: FilterOperator;
isNot: boolean;
attachedTo?: "node" | "relationship";
}) {
super();
this.attribute = attribute;
this.relationship = relationship;
this.comparisonValue = comparisonValue;
this.operator = operator;
this.isNot = isNot;
Expand All @@ -63,7 +69,7 @@ export class PropertyFilter extends Filter {
}

public getPredicate(queryASTContext: QueryASTContext): Cypher.Predicate {
const prop = this.getPropertyRef(queryASTContext);
const prop = this.getPropertyRefOrAliasesCase(queryASTContext);

if (this.comparisonValue === null) {
return this.getNullPredicate(prop);
Expand All @@ -74,6 +80,42 @@ export class PropertyFilter extends Filter {
return this.wrapInNotIfNeeded(baseOperation);
}

private getPropertyRefOrAliasesCase(queryASTContext: QueryASTContext): Cypher.Property | Cypher.Case {
const implementationsWithAlias = this.getAliasesToResolve();
if (implementationsWithAlias) {
return this.generateCaseForAliasedFields(queryASTContext, implementationsWithAlias);
}
return this.getPropertyRef(queryASTContext);
}

private getAliasesToResolve(): [string[], string][] | undefined {
if (!this.relationship || !(this.relationship.target instanceof InterfaceEntityAdapter)) {
return;
}
const aliasedImplementationsMap = this.relationship.target.getImplementationToAliasMapWhereAliased(
this.attribute
);
if (!aliasedImplementationsMap.length) {
return;
}
return aliasedImplementationsMap;
}

private generateCaseForAliasedFields(
queryASTContext: QueryASTContext,
concreteLabelsToAttributeAlias: [string[], string][]
): Cypher.Case {
if (!hasTarget(queryASTContext)) throw new Error("No parent node found!");
const aliasesCase = new Cypher.Case();
for (const [labels, databaseName] of concreteLabelsToAttributeAlias) {
aliasesCase
.when(queryASTContext.target.hasLabels(...labels))
.then(queryASTContext.target.property(databaseName));
}
aliasesCase.else(queryASTContext.target.property(this.attribute.databaseName));
return aliasesCase;
}

private getPropertyRef(queryASTContext: QueryASTContext): Cypher.Property {
if (this.attachedTo === "node") {
if (!hasTarget(queryASTContext)) throw new Error("No parent node found!");
Expand All @@ -88,7 +130,7 @@ export class PropertyFilter extends Filter {
/** Returns the operation for a given filter.
* To be overridden by subclasses
*/
protected getOperation(prop: Cypher.Property): Cypher.ComparisonOp {
protected getOperation(prop: Cypher.Property | Cypher.Case): Cypher.ComparisonOp {
return this.createBaseOperation({
operator: this.operator,
property: prop,
Expand Down Expand Up @@ -120,7 +162,7 @@ export class PropertyFilter extends Filter {
return expr;
}

private getNullPredicate(propertyRef: Cypher.Property): Cypher.Predicate {
private getNullPredicate(propertyRef: Cypher.Property | Cypher.Case): Cypher.Predicate {
if (this.isNot) {
return Cypher.isNotNull(propertyRef);
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,19 +131,22 @@ export class AuthFilterFactory extends FilterFactory {
operator,
isNot,
attachedTo,
relationship,
}: {
attribute: AttributeAdapter;
comparisonValue: unknown;
operator: WhereOperator | undefined;
isNot: boolean;
attachedTo?: "node" | "relationship";
relationship?: RelationshipAdapter;
}): PropertyFilter {
const filterOperator = operator || "EQ";

// This is probably not needed, but avoid changing the cypher
if (typeof comparisonValue === "boolean") {
return new ParamPropertyFilter({
attribute,
relationship,
comparisonValue: new Cypher.Param(comparisonValue),
isNot,
operator: filterOperator,
Expand All @@ -159,6 +162,7 @@ export class AuthFilterFactory extends FilterFactory {
if (isCypherVariable) {
return new ParamPropertyFilter({
attribute,
relationship,
comparisonValue: comparisonValue,
isNot,
operator: filterOperator,
Expand All @@ -168,6 +172,7 @@ export class AuthFilterFactory extends FilterFactory {
if (comparisonValue === null) {
return new PropertyFilter({
attribute,
relationship,
comparisonValue: comparisonValue,
isNot,
operator: filterOperator,
Expand All @@ -176,6 +181,7 @@ export class AuthFilterFactory extends FilterFactory {
}
return new ParamPropertyFilter({
attribute,
relationship,
comparisonValue: new Cypher.Param(comparisonValue),
isNot,
operator: filterOperator,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ export class FilterFactory {
return this.createInterfaceNodeFilters({
entity,
whereFields: value,
relationship: rel,
});
}
return this.createNodeFilters(entity, value);
Expand All @@ -164,12 +165,14 @@ export class FilterFactory {

protected createPropertyFilter({
attribute,
relationship,
comparisonValue,
operator,
isNot,
attachedTo,
}: {
attribute: AttributeAdapter;
relationship?: RelationshipAdapter;
comparisonValue: unknown;
operator: WhereOperator | undefined;
isNot: boolean;
Expand Down Expand Up @@ -197,6 +200,7 @@ export class FilterFactory {

return new PropertyFilter({
attribute,
relationship,
comparisonValue,
isNot,
operator: filterOperator,
Expand Down Expand Up @@ -267,10 +271,12 @@ export class FilterFactory {
entity,
targetEntity,
whereFields,
relationship,
}: {
entity: InterfaceEntityAdapter;
targetEntity?: ConcreteEntityAdapter;
whereFields: Record<string, any>;
relationship?: RelationshipAdapter;
}): Filter[] {
const filters = filterTruthy(
Object.entries(whereFields).flatMap(([key, value]): Filter | Filter[] | undefined => {
Expand Down Expand Up @@ -317,6 +323,7 @@ export class FilterFactory {
}
return this.createPropertyFilter({
attribute: attr,
relationship,
comparisonValue: value,
isNot,
operator,
Expand Down
Loading

0 comments on commit cb83cf5

Please # to comment.