【问题标题】:Parsing Apollo GraphQL query into Sequelize query with include and attributes fields将 Apollo GraphQL 查询解析为包含和属性字段的 Sequelize 查询
【发布时间】:2021-02-23 01:56:45
【问题描述】:

我正在与 Apollo 合作,为我的 GraphQL 请求构建解析器。

为了提高效率,我想获取请求的模型列表(带有各自的嵌套)以及每个模型请求的字段。这样我就可以将此信息传递给sequelize,以便仅在需要时加入模型 - 并且只提取必要的字段。

解析器会在 info 对象中传递此信息。

(obj, args, { models }, info) => ...

info 对象中,字段、嵌套模型及其各自的选定字段通过此路径公开:

info.fieldNodes[0].selectionSet.selections

我的问题是将这个结构(以我想象的某种递归方式)解析成一个合理的结构,以便我传递给sequelize 查询。

GraphQL 查询示例:

{
  getCompany(id: 1) {
    id
    name
    companyOffices {
      id
      users {
        id
        title
        userLinks {
          id
          linkUrl
        }
      }
    }
  }
}

info.fieldNodes[0].selectionSet.selections 上生成以下内容(为简洁起见,删除了一些字段):

[
   {
      "kind":"Field",
      "name":{
         "kind":"Name",
         "value":"id"
      }
   },
   {
      "kind":"Field",
      "name":{
         "kind":"Name",
         "value":"name"
      }
   },
   {
      "kind":"Field",
      "name":{
         "kind":"Name",
         "value":"companyOffices"
      },
      "selectionSet":{
         "kind":"SelectionSet",
         "selections":[
            {
               "kind":"Field",
               "name":{
                  "kind":"Name",
                  "value":"id"
               }
            },
            {
               "kind":"Field",
               "name":{
                  "kind":"Name",
                  "value":"users"
               },
               "selectionSet":{
                  "kind":"SelectionSet",
                  "selections":[
                     {
                        "kind":"Field",
                        "name":{
                           "kind":"Name",
                           "value":"id"
                        }
                     },
                     {
                        "kind":"Field",
                        "name":{
                           "kind":"Name",
                           "value":"title"
                        }
                     },
                     {
                        "kind":"Field",
                        "name":{
                           "kind":"Name",
                           "value":"userLinks"
                        },
                        "selectionSet":{
                           "kind":"SelectionSet",
                           "selections":[
                              {
                                 "kind":"Field",
                                 "name":{
                                    "kind":"Name",
                                    "value":"id"
                                 }
                              },
                              {
                                 "kind":"Field",
                                 "name":{
                                    "kind":"Name",
                                    "value":"linkUrl"
                                 }
                              }
                           ]
                        }
                     }
                  ]
               }
            }
         ]
      }
   }
]

使用这些信息,我想生成如下查询:

  const company = await models.Company.findOne({
    where: { id: args.id },
    attributes: // DYNAMIC BASED ON QUERY
    include: // DYNAMIC BASED ON QUERY
  });

所以我需要将上面的 GraphQL 查询从上面的 info 对象解析为类似这样的结构:

{
  attributes: ["id", "name"],
  include: [
    {
      model: "companyOffices",
      attributes: ["id"],
      include: [
        {
          model: users,
          attributes: ["id", "title"],
          include: [{ model: "userLinks", attributes: ["id", "linkUrl"] }]
        }
      ]
    }
  ]
};

但我不清楚如何通过递归来实现这一点,而不会让事情变得混乱。如果有更简单的方法来实现这种动态include/attributes 我也愿意。

tl;dr - 如何将 Apollo GraphQL 查询的模型和字段转移到 sequelize 查询的 includeattributes

【问题讨论】:

    标签: javascript recursion graphql sequelize.js apollo


    【解决方案1】:

    它可能会绕过这个问题,但我想知道像 graphql-sequelize 这样的东西是否可以帮助解决这样的问题。如果不是,我已经使用this strategy 来完成您问题的属性部分。

    const mapAttributes = (model, { fieldNodes }) => {
      // get the fields of the Model (columns of the table)
      const columns = new Set(Object.keys(model.rawAttributes));
      const requested_attributes = fieldNodes[0].selectionSet.selections
        .map(({ name: { value } }) => value);
      // filter the attributes against the columns
      return requested_attributes.filter(attribute => columns.has(attribute));
    };
    
    User: async (
        _, // instance (not used in Type resolver)
        { username, ... }, // arguments 
        { models: { User }, ... }, // context
        info,
      ) => {
        if (username) {  
          // (only requested columns queried)
          return User.findOne({
            where: { username },
            attributes: mapAttributes(User, info),
          });
        } ... 
      }
    

    【讨论】:

    • 我也读过这篇文章 - medium.freecodecamp.org/… 问题是它只涵盖了顶级查询的属性 - 而不是嵌套模型。
    • @BML91 你有办法解决这个问题吗?
    【解决方案2】:

    我终于找到了解决这个问题的方法(有点)。

    const mapAttributes = (projectors, models) => {
        const map = {};
        const set = [];
        Object.keys(projectors).forEach((projector) => {
            if (typeof projectors[projector] === 'object') {
                map[projector] = mapAttributes(projectors[projector], models.slice(1));
            } else {
                set.push(projector);
                map[models[0]] = set;
            }
        });
        return map;
    };
    

    这里projectors是graphql模式的对象表示法,和我们从info.fieldNodes[0].selectionSet.selections得到的对象一样

    和模型,是有序的迭代方式。所以在上面的例子中它会像

    ['company', 'companyOffices', 'users']

    从最终的地图中,我们进入一个整洁的结构,从中我们可以很好地获取属性。

    最后,当您返回参数时,您可能需要将 sequelize 输出转换为 graphql 类型

    const sequelizeToGraphql = (results = [], vouchers = [], postings = []) => {
        const final = { particulars: [] };
        results.forEach((result) => {
            vouchers.forEach((voucher) => {
                final[voucher] = result[voucher];
            });
            const obj = {};
            postings.forEach((posting) => {
                obj[posting] = result[`posting.${posting}`];
            });
            final.particulars.push(obj);
        });
        return final;
    };
    

    这里voucherspostings是我sequqlize中的表名,同样自己修改

    【讨论】:

      猜你喜欢
      • 2018-10-19
      • 1970-01-01
      • 2020-05-05
      • 2020-07-08
      • 1970-01-01
      • 2020-05-30
      • 1970-01-01
      • 2019-10-10
      • 2016-07-26
      相关资源
      最近更新 更多