Is there a way of assigning a UID to a field with type string OR can I sort the parent node by an edge UID in GraphQL?

Consider the following schema

interface Sortable {
  id: ID!
  sortings: [Sorting!]!
}

type Sorting {
  id: ID!
  sequence: Int!
  contextRef: String! @search(by: [exact])
}

type Course {
  id: ID!
  title: String!
  chapters: [Chapter!]
}

type Chapter implements Sortable {
  id: ID!
  title: String!
  
  # from Sortable
  # sortings: [Sorting!]!
}

So one Course can have multiple Chapters which again have multiple Sortings. I have multiple sortings because I want to allow that one chapter can also belong to many courses but with a different sorting. Here’s a sketch to visualise the data model:

Schematic data model

Hence the contextRef basically tells me in which context (in this case type Course) one specific sorting is valid. I should mention that the context reference not necessarily needs to be the parent node!

I have contextRef of type String because I need the ability to filter sortings according to the scope I’m dealing with.

query Course($id: ID!, $string_id: String!) {
  queryCourse(filter: { id: $id }) {
    id
    title
    chapters {
      id
      title
      sortings(filter: { contextRef: { eq: $string_id } }) {
        sequence
      }
   }
  }
}

Note that $id and $string_id are the same values - specifically the course UID.

So far so good. This will work but I have problems with assigning contextRef properly in a single upsert block.

1. Assiging a UID as a string

If I create a new chapter inside a course, I would run this DQL upsert mutation. I have to run the query since the parent node of Chapter is not necessarily the reference node! Thus, I cannot rely on getting the correct reference UID submitted in my custom resolver.

{
  "query": "{ qCourse(func: uid(<ID>)) { c as uid } }",
  "set": {
    "uid": "uid(c)",
    "Course.chapters": {
      "dgraph.type": ["Chapter", "Sortable"],
      "Chapter.title": "Chapter Z",
      "Sorting.sequence": 0,
      "Sorting.contextRef": "uid(c)"
    }
  }
}

Unfortunately extracting “uid(c)” for a field of type string is prohibited and thus this is not an option.

Is there a way to assign a UID to a value variable which can be extracted with val()?

2. Filtering by an edge UID

The other approach would be having a proper edge to the node which serves as reference. Thus, I’d extend the schema like so

interface Node {
  id: ID!
}

interface Sortable {
  id: ID!
  sortings: [Sorting!]!
}

type Sorting {
  id: ID!
  sequence: Int!
  contextRef: Node!
}

type Course implements Node {
  id: ID!
  title: String!
  chapters: [Chapter!]
}

type Chapter implements Sortable {
  id: ID!
  title: String!
  
  # from Sortable
  # sortings: [Sorting!]!
}

Now I can easily assign the reference node to the new chapter by

{
  "query": "{ qCourse(func: uid(<ID>)) { c as uid } }",
  "set": {
    "uid": "uid(c)",
    "Course.chapters": {
      "dgraph.type": ["Chapter", "Sortable"],
      "Chapter.title": "Chapter Z",
      "Sortable.sortings": {
        "dgraph.type": "Sorting",
        "Sorting.sequence": 0,
        "Sorting.contextRef": "uid(c)"
      }
    }
  }
}

The problem is that I have no clue if I could filter by an edge UID but I guess this is not possible.

query Course($id: ID!) {
  queryCourse(filter: { id: $id }) {
    id
    title
    chapters {
      id
      title
      sortings(filter: { contextRef: {id: $id} }) {
        sequence
      }
   }
  }
}

Is there a way of filtering by an edge UID?

The only solution that I found so far, is having an additional @id field for all nodes which are referenceable and either mapping the UID to this field or introducing UUIDs. The concept with UUIDs would allow a single DQL mutation on create, whereas the “mapping” of the UID would require to generate the node first and afterwards assign the UID (as string) in a separate mutation.

Any help appreciated!

I found the solution. Totally forgot about @cascade. :see_no_evil: I can implement a Node interface to all types (or at least the types I want to be referencable by Sorting). Sorting then references to Node and I a combination of @cascade on Sorting and @filter on contextRef does the job.

Additionally, introducing the Node interface and applying it to all types has additional advantages in case you use Relay. I’ve wrote a post about this in case someone is interested

Full example

Schema
 interface Node {
  id: ID!
}

interface Sortable {
  id: ID!
  sortings: [Sorting!]!
}

type Sorting {
  id: ID!
  sequence: Int!
  contextRef: Node!
}

type Course implements Node {
  id: ID!
  title: String!
  chapters: [Chapter!]
}

type Chapter implements Sortable {
  id: ID!
  title: String!
  
  # from Sortable
  # sortings: [Sorting!]!
}
Filtered query
query CourseQuery($id: ID!) {
  queryCourse(filter: { id: [$id] }) {
    chapters {
      sortings @cascade(fields: ["contextRef"]) {
        sequence
        contextRef(filter: { id: [$id] }) {
          id
        }
      }
    }
  }
}