SoftInstigate / restheart

Open Source Low-code API development framework Featuring ready-to-go Security and MongoDB API
http://softinstigate.github.io/restheart/
GNU Affero General Public License v3.0
792 stars 170 forks source link

Add rootDoc $arg to GraphQL mappings #469

Closed ujibang closed 8 months ago

ujibang commented 11 months ago

Brief overview

Allow to use {"$arg": "rootDoc"} predefined variable to GraphQL query mapping and field to query/aggregation mappings.

Rationale

Given the following documents in a mongodb collection:

[
  {
    _id: 'foo',
    _etag: ObjectId("64a6a6cde3acd23a25a20087"),
    posts: [
      { content: 'ping', visible: true },
      { content: 'pong', visible: true },
      { content: 'invisible', visible: false }
    ]
  },
  {
    _id: 'bar',
    _etag: ObjectId("64a6a6cde3acd23a25a20087"),
    posts: [
      { content: 'ping', visible: true },
      { content: 'pong', visible: true },
      { content: 'invisible', visible: false }
    ]
  },
  {
    _id: 'zum',
    _etag: ObjectId("64a6a6cde3acd23a25a20087"),
    posts: [
      { content: 'ping', visible: true },
      { content: 'pong', visible: true },
      { content: 'invisible', visible: false }
    ]
  }
]

Consider the following GraphQL schema

type User {
  _id: String
  posts(visible: Boolean): [Post]
}
type Post {
  content: String
}
type Query {
  users: [User]
}

In order to filter the nested document objects of the posts array we can make use of field to aggregation mapping:

{
  "User": {
    "posts": {
      "db": "restheart",
      "collection": "the-users",
      "stages": [ 
        { "$match": { "_id": { "$fk": "_id" } } },
        { "$unwind" : "$posts"  },
        { "$replaceRoot": {"newRoot": "$posts"} },
        { "$match": { "visible": { "$arg": "visible" } } }
    ]
    }
  }
}

It basically maps the posts field to an aggregation that finds the root document, and applies stages to get the filtered posts.

This solution relies on the $fk operator to match the root document.

However this approach only works only for the first nesting level, i.e. if won't work for documents like the following:

  {
  "_id": "bar",
  "sub": {
    "posts": [
      { "content": "ping", "visible": true },
      { "content": "pong", "visible": true },
      { "content": "invisible", "visible": false }
    ]
  }
}

The new predefined variable {"$arg": "rootDocId"} will allow this use case:

{
  "User": {
    "posts": {
      "db": "restheart",
      "collection": "the-users",
      "stages": [ 
        { "$match": { "_id": { "$arg": "rootDoc._id" } } }, <---
        { "$unwind" : "$sub.posts"  },
        { "$replaceRoot": {"newRoot": "$sub.posts"} },
        { "$match": { "visible": { "$arg": "visible" } } }
    ]
    }
  }
}

Detailed documentation

https://restheart.org/docs/mongodb-graphql/#the-rootdoc-argument

ujibang commented 8 months ago

docs at https://restheart.org/docs/mongodb-graphql/#the-rootdoc-argument