12

我试图找出一种处理查询和 mongdb 投影的干净方法,这样我就不必从数据库中检索过多的信息。所以假设我有:

// the query
type Query {
  getUserByEmail(email: String!): User
}

我有 aUseremaila username,以保持简单。如果我发送查询并且只想检索电子邮件,我可以执行以下操作:

query { getUserByEmail(email: "test@test.com") { email } }

但是在解析器中,我的数据库查询仍然检索usernameemail,但只有其中一个被阿波罗服务器作为查询结果传回。

我只希望数据库检索查询要求的内容:

// the resolver
getUserByEmail(root, args, context, info) {
  // check what fields the query requested
  // create a projection to only request those fields
  return db.collection('users').findOne({ email: args.email }, { /* projection */ });
}

当然,问题是,获取有关客户请求的信息并不是那么简单。

假设我将请求作为上下文传递 - 我考虑使用context.payload具有查询字符串的 (hapi.js) 并通过各种.split()s 搜索它,但这感觉有点脏。据我所知,info.fieldASTs[0].selectionSet.selections有字段列表,我可以检查它是否存在。我不确定这有多可靠。特别是当我开始使用更复杂的查询时。

有没有更简单的方法?

如果您不使用 mongDB,则投影是您传入的附加参数,明确告诉它要检索什么:

// telling mongoDB to not retrieve _id
db.collection('users').findOne({ email: 'test@test.com' }, { _id: 0 })

一如既往,感谢这个了不起的社区。

4

5 回答 5

5

2020-1月答案

获取 GraphQL 查询中请求的字段的当前答案是使用graphql-parse-resolve-info库来解析info参数。

该库是“一个非常完整的解决方案,实际上在后台被 postgraphile 使用”,并且被其他顶级库的作者推荐info用于解析该字段,graphql-fields.

于 2020-01-23T03:24:08.797 回答
1

使用graphql 字段

Apollo 服务器示例

const rootSchema = [`

    type Person {
        id: String!
        name: String!
        email: String!
        picture: String!
        type: Int!
        status: Int!
        createdAt: Float
        updatedAt: Float
    }

    schema {
    query: Query
    mutation: Mutation
    }

`];

const rootResolvers = {


    Query: {

        users(root, args, context, info) {
            const topLevelFields = Object.keys(graphqlFields(info));
            return fetch(`/api/user?fields=${topLevelFields.join(',')}`);
        }
    }
};

const schema = [...rootSchema];
const resolvers = Object.assign({}, rootResolvers);

// Create schema
const executableSchema = makeExecutableSchema({
    typeDefs: schema,
    resolvers,
});
于 2017-07-22T01:54:49.180 回答
0

你当然可以。这实际上与在基于 SQL 的数据库的 join-monster 包上实现的功能相同。他们的创造者有一个谈话:https ://www.youtube.com/watch?v=Y7AdMIuXOgs

查看他们的info分析代码以帮助您入门 - https://github.com/stems/join-monster/blob/master/src/queryASTToSqlAST.js#L6-L30

很想为我们的 mongo 用户看到一个投影怪物包:)

更新:有一个包可以从infonpm 上创建一个投影对象:https ://www.npmjs.com/package/graphql-mongodb-projection

于 2016-11-20T11:15:23.647 回答
0

info您可以从参数生成 MongoDB 投影。这是您可以遵循的示例代码

 /**
 * @description - Gets MongoDB projection from graphql query
 *
 * @return { object }
 * @param { object } info
 * @param { model } model - MongoDB model for referencing
 */

function getDBProjection(info, model) {
  const {
    schema: { obj }
  } = model;
  const keys = Object.keys(obj);
  const projection = {};

  const { selections } = info.fieldNodes[0].selectionSet;

  for (let i = 0; i < keys.length; i++) {
    const key = keys[i];
    const isSelected = selections.some(
      selection => selection.name.value === key
    );

    projection[key] = isSelected;
  }

  console.log(projection);
}

module.exports = getDBProjection;

于 2020-02-19T07:04:09.853 回答
0

使用一些辅助函数,您可以像这样使用它(打字稿版本):

import { parceGqlInfo, query } from "@backend";
import { GraphQLResolveInfo } from "graphql";

export const user = async (parent: unknown, args: unknown, ctx: unknown, info: GraphQLResolveInfo): Promise<User | null> => {
  const { dbQueryStr } = parceGqlInfo(info, userFields, "id");

  const [user] = await query(`SELECT ${dbQueryStr} FROM users WHERE id=$1;`, [1]);

  return user;
};

辅助函数。

几点:

  • gql_uid 用作 ID!从主键到不更改数据库类型的字符串类型

  • required 选项用于数据加载器(如果用户未请求字段)

  • allowedFields 用于从诸如“__typename”之类的信息中过滤其他字段

  • 如果您需要为所选字段添加前缀,则使用 queryPrefixselect u.id from users u

    const userFields = [
           "gql_uid",
           "id",
           "email"
         ]
    
     // merge arrays and delete duplicates
     export const mergeDedupe = <T>(arr: any[][]): T => {
       // @ts-ignore
       return ([...new Set([].concat(...arr))] as unknown) as T;
     };
    
     import { parse, simplify, ResolveTree } from "graphql-parse-resolve-info";
     import { GraphQLResolveInfo } from "graphql";
    
     export const getQueryFieldsFromInfo = <Required = string>(info: GraphQLResolveInfo, options: { required?: Required[] } = {}): string[] => {
       const { fields } = simplify(parse(info) as ResolveTree, info.returnType) as { fields: { [key: string]: { name: string } } };
    
       let astFields = Object.entries(fields).map(([, v]) => v.name);
    
       if (options.required) {
         astFields = mergeDedupe([astFields, options.required]);
       }
    
       return astFields;
     };
    
     export const onlyAllowedFields = <T extends string | number>(raw: T[] | readonly T[], allowed: T[] | readonly T[]): T[] => {
       return allowed.filter((f) => raw.includes(f));
     };
    
     export const parceGqlInfo = (
       info: GraphQLResolveInfo,
       allowedFields: string[] | readonly string[],
       gqlUidDbAlliasField: string,
       options: { required?: string[]; queryPrefix?: string } = {}
     ): { pureDbFields: string[]; gqlUidRequested: boolean; dbQueryStr: string } => {
       const fieldsWithGqlUid = onlyAllowedFields(getQueryFieldsFromInfo(info, options), allowedFields);
    
       return {
         pureDbFields: fieldsWithGqlUid.filter((i) => i !== "gql_uid"),
         gqlUidRequested: fieldsWithGqlUid.includes("gql_uid"),
         dbQueryStr: fieldsWithGqlUid
           .map((f) => {
             const dbQueryStrField = f === "gql_uid" ? `${gqlUidDbAlliasField}::Text AS gql_uid` : f;
    
             return options.queryPrefix ? `${options.queryPrefix}.${dbQueryStrField}` : dbQueryStrField;
           })
           .join(),
       };
    

    };

于 2020-08-04T17:02:58.520 回答