Skip to content

Apoc usage

angrykoala edited this page Jul 5, 2022 · 44 revisions

This page documents current usage of apoc and any known alternative

Location Procedure File Notes
Cypher Projection runFirstColumn src/translate/create-projection-and-params.ts -
Cypher Resolver runFirstColumn src/schema/resolvers/field/cypher.ts No alternative found
Aggregate Where runFirstColumn src/translate/create-aggregate-where-and-params.ts -
String aggregations runFirstColumn src/translate/translate-aggregate.ts Solved in #1679
Field Aggregations Count runFirstColumn src/translate/field-aggregations/create-field-aggregation.ts -
Field Aggregations Projection runFirstColumn src/translate/field-aggregations/create-field-aggregation.ts -
Connection Projection runFirstColumn src/translate/create-projection-and-params.ts -
Query Points runFirstColumn src/translate/projection/elements/create-point-element.ts -
Create Element Where runFirstColumn src/translate/where/create-element-where-and-params.ts -

Run First Column

Cypher Projection

Used for @cypher directive.

Example

Schema

type Movie {
  id: ID
  title: String
  topActor: Actor
    @cypher(
      statement: """
          MATCH (a:Person)
          RETURN a
      """
    )
}

type Actor {
  name: String
}

Query

{
  movies {
    title
    topActor {
      name
    }
  }
}

Cypher

MATCH (this:Movie)
RETURN this { 
    .title, 
    topActor: head([this_topActor IN apoc.cypher.runFirstColumn("MATCH (a:Person)
            RETURN a", {this: this}, false) | this_topActor { .name }]) 
} as this

Proposed Cypher

MATCH(m:Movie)
CALL {
    WITH m
    WITH m as this
    MATCH (a:Person)
    RETURN a
}
WITH m, collect(a) as projection
RETURN m {.title, topActor: head([this_topActor IN projection | this_topActor { .name }]) }

Considerations

  • Return multiple items in an array vs a single by using head in the projection.
  • We need to keep track of subqueries for projection.

Cypher Resolver

Schema

type Person {
    actorId: ID!
    name: String
}

type Query {
    allPeople: [Person]
        @cypher(
            statement: """
            MATCH (a:Person)
            RETURN a
            """
        )
}

Query

{
	allPeople {
	    name
	}
}

Cypher

WITH apoc.cypher.runFirstColumn("
	MATCH (a:Person)
	RETURN a
", {}, true) as x
UNWIND x as this
WITH this
RETURN this { .name } AS this

Proposed Cypher

TODO

Considerations

  • Some unexpected behaviour may happen when returning lists due to the extra UNWIND.
  • Returned values from user may have multiple names

Aggregate Where

Use for aggregation where (e.g. count)

Example

Schema

type User {
	name: String!
}

type Post {
	content: String!
	likes: [User!]! @relationship(type: "LIKES", direction: IN)
}

Query

{
	posts(where: { likesAggregate: { count: 10 } }) {
		content
	}
}

Cypher

MATCH (this:Post)
WHERE apoc.cypher.runFirstColumn(" MATCH (this)<-[this_likesAggregate_edge:LIKES]-(this_likesAggregate_node:User)
RETURN count(this_likesAggregate_node) = $this_likesAggregate_count
", { this: this, this_likesAggregate_count: $this_likesAggregate_count }, false )
RETURN this { .content } as this

Proposed Cypher

MATCH(this:Post)
CALL {
    WITH this
    MATCH(this)<-[:LIKES]-(u:User)
    RETURN count(u) as agg_count
}
WITH *
WHERE agg_count=10
RETURN m {.title} as this

Considerations

  • Keep track of subqueries for where statement.

String aggregations

Aggregations use first column for string returns in aggregation

Example

Schema

type Movie {
    title: String!
}

Query

{
moviesAggregate {
    title {
	shortest
        }
    }
}

Cypher

MATCH (this:Movie)
RETURN { title: { shortest:
reduce(shortest = collect(this.title)[0], current IN collect(this.title) | apoc.cypher.runFirstColumn("
	RETURN
	CASE size(current) < size(shortest)
	WHEN true THEN current
	ELSE shortest
	END AS result
", { current: current, shortest: shortest }, false))
  }
}

Proposed Cypher

MATCH (this:Movie)
RETURN { title: { shortest:
reduce(shortest = collect(this.title)[0], current IN collect(this.title) | 
	CASE size(current) < size(shortest)
	WHEN true THEN current
	ELSE shortest
	END
	)
}} as this

Field Aggregations Count

Field aggregations use runInColumn for count values

Example

Schema

type Movie {
	title: String
	actors: [Actor!]! @relationship(type: "ACTED_IN", direction: IN)
	released: DateTime
}

type Actor {
	name: String
	age: Int
	movies: [Movie!]! @relationship(type: "ACTED_IN", direction: OUT)
}

Query

query {
	movies {
		title
		actorsAggregate {
			count
		}
	}
}

Cypher

MATCH (this:Movie)
RETURN this { .title, 
	actorsAggregate: { 
		count: head(apoc.cypher.runFirstColumn("MATCH (this)<-[r:ACTED_IN]-(n:Person)      RETURN COUNT(n)", { this: this })) 
		} 
	} as this

Proposed Cypher

MATCH (this:Movie)
CALL {
    WITH this
    MATCH (this)<-[r:ACTED_IN]-(n:Person)
    RETURN COUNT(n) as person_count
}
WITH *
RETURN this { .title, 
	actorsAggregate: { 
		count: person_count
		} 
} as this

Field Aggregations Projection

Field aggregation subquery is projected through runFirstColumn

Example

Schema

type Movie {
	title: String
	actors: [Actor!]! @relationship(type: "ACTED_IN", direction: IN)
}

type Actor {
	name: String
	age: Int
	movies: [Movie!]! @relationship(type: "ACTED_IN", direction: OUT)
}

Query

query {
	movies {
		actorsAggregate {
			node {
				age {
					min
					max
					average
					sum
				}
			}
		}
	}
}

Cypher

MATCH (this:Movie)
RETURN this { actorsAggregate: { node: { age: head(apoc.cypher.runFirstColumn("MATCH (this)<-[r:ACTED_IN]-(n:Person)
    RETURN {min: min(n.age), max: max(n.age), average: avg(n.age), sum: sum(n.age)}", { this: this })) } } } as this

Proposed Cypher

MATCH (this:Movie)
CALL {
    WITH this
	MATCH(this)<-[r:ACTED_IN]-(n:Person)
	RETURN {min: min(n.age), max: max(n.age), average: avg(n.age), sum: sum(n.age)} AS agg_data
}
WITH *
RETURN this { actorsAggregate: { node: agg_data } } as this

Query Points

Example

Schema

type PointContainer {
  point: Point
}

Query

{
  pointContainers(where: { point: { longitude: 1.0, latitude: 2.0 } }) {
    point {
      longitude
      latitude
      crs
    }
  }
}

Cypher

MATCH (this:PointContainer)
WHERE this.point = point($this_point)
RETURN this { point: apoc.cypher.runFirstColumn('RETURN
            CASE this.point IS NOT NULL
            	WHEN true THEN { point: this.point, crs: this.point.crs }
            	ELSE NULL
            END AS result',{ this: this },false) 
} as this

Proposed Cypher

MATCH(this:PointContainer)
WITH this,
CASE this.point IS NOT NULL
  WHEN true THEN {point: this.point, crs: this.point.crs}
  ELSE NULL
END AS this_point
RETURN this {.name, point: this_point} as this

Considerations

  • We need to keep track of subqueries for projection.

Create Element Where

WIP

Clone this wiki locally