Can I get all related documents, independent of the type?

I’m thinking about the possibility of using Dgraph in a project. But there is a requirement that I’m not sure if I can accomplish using GraphQL or DQL.

I will have about 8-10 types of documents. Every type would be linked with each other using a certain field. And, in the end, every document will be related to an “User” type somehow (not directly, but following a “path” between this document types, with certain depth).

What I need to do to fulfill some requirements is to query all documents linked to an specific User (paginated), ordered by the document creation date/time. It would be like an “historic event stream view” of all user-related data.

For now, I have no clue if Dgraph could help me to get this done. Have anybody done something like that already? Or have any thoughts about how to achieve it?

Thanks in advance!

1 Like

It all depends on how you set up your database schema and structure of course, but it sounds doable in DQL. Maybe a better definition of the query pattern would uncover something difficult but I do not see anything impossible there.

1 Like

You could easily use either DQL or GraphQL.

Example:

type User {
  uid: ID!
  name: String!
  docs: [Document] @hasInverse(field: user)
  ...
}
type Document {
  id: ID!
  type: docType
  name: String
  content: String
  user: [User]
  ...
}
type docType {
  id: ID!
  name: String!
  fields: string
  ...  (some type info here)
}

and you could have a query like this:

query {
  getUser(uid: 0x0223) {
    docs {
      id
      type {
        id
        name
        fields
      }
      name
      content
      ...
    }
  }
}

or something similar… dql is a bit more complicated, but also a bit more flexible. But yes, it can be done. It depends on your exact data, but you could set it up in many ways.

J

1 Like

Thank you very much for the (really fast!) replies and tips/enlightments!

Your reply gave me some hope that I’m not too crazy thinking about Dgraph to fulfill this requirements, @iluminae :crazy_face:

This structure, @jdgamble555, maybe wouldn’t fit for other operations of the application. When I’ve written “type”, I meant a different schema type. Something like this:

interface Record {
  uid: ID!
  updatedAt: DateTime!
}

type User implements Record {
  uid: ID!
  name: String!
  updatedAt: DateTime!
  ...
}

type TypeA implements Record {
  id: ID!
  users: [User]
  updatedAt: DateTime!
  ...
}

type TypeB implements Record {
  id: ID!
  typeA: TypeA
  updatedAt: DateTime!
  ...
}

type TypeC implements Record {
  id: ID!
  typeB: TypeB
  updatedAt: DateTime!
  ...
}

I don’t have a defined schema yet, but the main structure would be “independent” of the User type (as I tried to show above, based on your sample schema), because of other structure/architecture rules. Some types even will be linked to User in an indirect way (like TypeB and TypeC, that would follow a path over TypeA).

The requirement regarding the user is because we need to return all of its relations with other schema types (TypeA, TypeB, TypeC, … TypeN). And, as I stated, it should be in an ordered way (that is why I’m thinking about having an interface to expose an “updatedAt” field or something like that, so I can get results ordered by date).

The query result should be something like this (it would be paginated, to avoid massive payloads as a result):

{
  "data": [{
    }, {
       // TypeA data...
       createdAt: "2021-11-11T09:47:22.000Z"
    }, {
       // TypeC data...
       createdAt: "2021-11-11T09:46:07.000Z"
    }, {
       // TypeA data...
       createdAt: "2021-11-11T09:42:33.000Z"
    }, {
       // TypeA data...
       createdAt: "2021-11-11T09:39:21.000Z"
    }, {
       // TypeB data...
       createdAt: "2021-11-11T09:39:13.000Z"
    },
    ...
  ]
}

It makes sense thinking about how Dgraph/DQL works? I’m considering DQL because, as this path traversals are more graph-specific, I don’t think GraphQL would give me the expected results.

Thanks for the attention!

1 Like

Those results would simply be achived in GraphQL by querying the interface instead of querying the implemented types. Super easy there.

In DQL there are no interfaces, but that is not a problem obviously. So how to model it in DQL? Simply as all types. In DQL a single node can have multiple types. And then you can just query the less or more specific type(s) that you want. You could search for data that had both types or either type depending on what your use case is.

In DQL if you wanted to do an either type query, you would put something different in the root filter such as has(createdAt) and then in your filter directive do or logic combiner with two type filters. @filter(type(Record) or type(User))

1 Like

Thanks for the clarification and tips!

I have no idea that I could query interfaces to get the types that implement it. It is a possibility for that use case, indeed!

But, this way, how could I filter the records considering only the related to an specific “User”? Should I write custom filters for all the types, somehow?

I would modify your example schema above with inverse edges if using GraphQL like the following

interface Record {
  id: ID!
  updatedAt: DateTime!
}

type User implements Record {
  name: String!
  typeAInverse: [TypeA] @hasInverse(field: "users")
  # ...
}

type TypeA implements Record {
  users: [User]
  typeBInverse: [TypeB] @hasInverse(field: "typeA")
  # ...
}

type TypeB implements Record {
  typeA: TypeA
  typeCInverse: [TypeC] @hasInverse(field: "typeC")
  # ...
}

type TypeC implements Record {
  typeB: TypeB
  # ...
}

NOTE: If you use DQL and not GraphQL you could just add the @reverse directive. And also note that in Dgraph, when creating a schema with types implementing interfaces you do not have to define the repeated fields in every type. They auto inherit all fields from the interface. :exploding_head:

Then you could traverse the graph from the user.

query {
  getUser(id:"0x2") {
    ...RecordFields
    typeAInverse {
      ...RecordFields
      typeBInverse {
        ...RecordFields
        typeCInverse {
          ...RecordFields
        }
      }
    }
  }
  fragment RecordFields on Record {
    id
    updatedAt
    # ...
  }
}

If you need it to be a flat list then you could use DQL and the normalize directive.

If you wanted to group all of these branches together into one trunk to paginate, you would do that with DQL (I will use the DQL schema that would be generated by my GraphQL schema above. This could be simplified to only DQL with reverse directive on edges):

query {
  user as var(func: uid("0x2")) {
    typeA as User.typeAInverse {
      typeB as TypeA.typeBInverse {
        typeC as TypeB.typeCInverse
      }
    }
  }
  records(func: uid(user,typeA,typeB,typeC), orderdesc: Record.updatedAt, first: 5, offset: 5) {
    uid
    Record.updatedAt
    # ...
    # Could also query things here like `User.name` or `TypeA.users`, etc.
  }
}

For completeness here would be a simple DQL schema:

updatedAt: datetime
name: string
users: [uid] @reverse
typeA: [uid] @reverse
typeB: [uid] @reverse
type Record {
  updatedAt
}
type User {
  name
  updatedAt
}
type TypeA {
  users
  updatedAt
}
type TypeB {
  typeA
  updatedAt
}
type TypeC {
  typeB
  updatedAt
}

and simplified DQL query equal to above using reverse ~

query {
  user as var(func: uid("0x2")) {
    typeA as ~users {
      typeB as ~typeA {
        typeC as ~typeC
      }
    }
  }
  records(func: uid(user,typeA,typeB,typeC), orderdesc: updatedAt, first: 5, offset: 5) {
    uid
    updatedAt
    # ...
    # Could also query things here like `name` or `users`, etc.
  }
}
2 Likes

Thank you (all) very much! :smiley:

It really helped me a lot these ideas on how to setup an schema to fulfill the requirements of this use case.

I will investigate a little further on how the schema could be defined based on these tips. But I think my doubts are now answered.

2 Likes