我正在尝试找出一种处理查询和mongdb投影的简洁方法,因此我不必从数据库中检索过多的信息。 假设我有:
// the query
type Query {
getUserByEmail(email: String!): User
}
我有User
email
和username
,以保持简单。如果我发送查询而我只想检索电子邮件,我可以执行以下操作:
query { getUserByEmail(email: "test@test.com") { email } }
但是在解析器中,我的数据库查询仍然检索username
和email
,但只有其中一个由apollo服务器作为查询结果传回。
我只希望数据库检索查询要求的内容:
// 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()
搜索它,但这感觉有点脏。据我所知,info.fieldASTs[0].selectionSet.selections
有字段列表,我可以检查它是否存在。我不确定这有多可靠。特别是当我开始使用更复杂的查询时。
有更简单的方法吗?
如果您不使用mongDB,投影是您传递的另一个参数,告诉它明确要检索的内容:
// telling mongoDB to not retrieve _id
db.collection('users').findOne({ email: 'test@test.com' }, { _id: 0 })
一如既往,感谢这个令人惊叹的社区。 p>
答案 0 :(得分:2)
当然可以。这实际上与基于SQL的db的join-monster包实现的功能相同。他们的创作者有一个演讲:https://www.youtube.com/watch?v=Y7AdMIuXOgs
查看他们的info
分析代码,以帮助您入门 - https://github.com/stems/join-monster/blob/master/src/queryASTToSqlAST.js#L6-L30
很想为我们的mongo用户看一个投影怪物包:)
<强>更新强>: 有一个包在{npm:https://www.npmjs.com/package/graphql-mongodb-projection
上从info
创建投影对象
答案 1 :(得分:2)
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,
});
答案 2 :(得分:1)
获取GraphQL查询中请求的字段的当前答案是使用graphql-parse-resolve-info
库来解析info
参数。
该库为“ a pretty complete solution and is actually used under the hood by postgraphile”,另一个顶级库的作者继续recommended来解析info
字段graphql-fields
。
答案 3 :(得分: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;
答案 4 :(得分: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!字符串类型从主键更改为不更改数据库类型
必需的选项用于数据加载器(如果用户未请求该字段)
allowedFields用于从“ __typename”之类的信息中过滤其他字段
select u.id from users u
之类的选定字段添加前缀,请使用queryPrefix
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(),
};
};