从 GraphQL 请求构造 MongoDB 查询
Construct MongoDB query from GraphQL request
假设我们用这个请求查询服务器,我们只想得到以下用户的电子邮件,我当前的实现从 MongoDB 请求整个用户对象,我可以想象这是非常低效的。
GQL
{
user(id:"34567345637456") {
email
}
}
您将如何着手创建一个 MongoDB 过滤器,它只会 return 那些指定的字段?例如,
JS object
{
"email": 1
}
我当前的服务器是 运行 Node.js、Fastify 和 Mercurius
which I can imagine is extremely inefficient.
执行此任务是一项高级功能,但有很多陷阱。我建议开始构建一个读取所有字段的简单提取。此解决方案有效并且不会 return 客户端的任何附加字段。
陷阱是:
- 嵌套查询
- 复杂的对象组合
- 别名
- 多个查询合并为一个请求
这是一个可以满足您要求的示例。
它管理别名和多个查询。
const Fastify = require('fastify')
const mercurius = require('mercurius')
const app = Fastify({ logger: true })
const schema = `
type Query {
select: Foo
}
type Foo {
a: String
b: String
}
`
const resolvers = {
Query: {
select: async (parent, args, context, info) => {
const currentQueryName = info.path.key
// search the input query AST node
const selection = info.operation.selectionSet.selections.find(
(selection) => {
return (
selection.name.value === currentQueryName ||
selection.alias.value === currentQueryName
)
}
)
// grab the fields requested by the user
const project = selection.selectionSet.selections.map((selection) => {
return selection.name.value
})
// do the query using the projection
const result = {}
project.forEach((fieldName) => {
result[fieldName] = fieldName
})
return result
},
},
}
app.register(mercurius, {
schema,
resolvers,
graphiql: true,
})
app.listen(3000)
使用以下方式调用它:
query {
one: select {
a
}
two: select {
a
aliasMe:b
}
}
Returns
{
"data": {
"one": {
"a": "a"
},
"two": {
"a": "a",
"aliasMe": "b"
}
}
}
从 原始答案扩展,他在其中指出他的实现的缺陷之一是它不适用于嵌套查询和 'multiple queries into one request' 此实现试图修复的问题。
function formFilter(context:any) {
let filter:any = {};
let getValues = (selection:any, parentObj?:string[]) => {
//selection = labelSelection(selection);
selection.map((selection:any) => {
// Check if the parentObj is defined
if(parentObj)
// Merge the two objects
_.merge(filter, [...parentObj, null].reduceRight((obj, next) => {
if(next === null) return ({[selection.name?.value]: 1});
return ({[next]: obj});
}, {}));
// Check for a nested selection set
if(selection.selectionSet?.selections !== undefined){
// If the selection has a selection set, then we need to recurse
if(!parentObj) getValues(selection.selectionSet?.selections, [selection.name.value]);
// If the selection is nested
else getValues(selection.selectionSet?.selections, [...parentObj, selection.name.value]);
}
});
}
// Start the recursive function
getValues(context.operation.selectionSet.selections);
return filter;
}
输入
{
role(id: "61f1ccc79623d445bd2f677f") {
name
users {
user_name
_id
permissions {
roles
}
}
permissions
}
}
输出(JSON.stringify)
{
"role":{
"name":1,
"users":{
"user_name":1,
"_id":1,
"permissions":{
"roles":1
}
},
"permissions":1
}
}
假设我们用这个请求查询服务器,我们只想得到以下用户的电子邮件,我当前的实现从 MongoDB 请求整个用户对象,我可以想象这是非常低效的。
GQL
{
user(id:"34567345637456") {
email
}
}
您将如何着手创建一个 MongoDB 过滤器,它只会 return 那些指定的字段?例如,
JS object
{
"email": 1
}
我当前的服务器是 运行 Node.js、Fastify 和 Mercurius
which I can imagine is extremely inefficient.
执行此任务是一项高级功能,但有很多陷阱。我建议开始构建一个读取所有字段的简单提取。此解决方案有效并且不会 return 客户端的任何附加字段。
陷阱是:
- 嵌套查询
- 复杂的对象组合
- 别名
- 多个查询合并为一个请求
这是一个可以满足您要求的示例。 它管理别名和多个查询。
const Fastify = require('fastify')
const mercurius = require('mercurius')
const app = Fastify({ logger: true })
const schema = `
type Query {
select: Foo
}
type Foo {
a: String
b: String
}
`
const resolvers = {
Query: {
select: async (parent, args, context, info) => {
const currentQueryName = info.path.key
// search the input query AST node
const selection = info.operation.selectionSet.selections.find(
(selection) => {
return (
selection.name.value === currentQueryName ||
selection.alias.value === currentQueryName
)
}
)
// grab the fields requested by the user
const project = selection.selectionSet.selections.map((selection) => {
return selection.name.value
})
// do the query using the projection
const result = {}
project.forEach((fieldName) => {
result[fieldName] = fieldName
})
return result
},
},
}
app.register(mercurius, {
schema,
resolvers,
graphiql: true,
})
app.listen(3000)
使用以下方式调用它:
query {
one: select {
a
}
two: select {
a
aliasMe:b
}
}
Returns
{
"data": {
"one": {
"a": "a"
},
"two": {
"a": "a",
"aliasMe": "b"
}
}
}
从
function formFilter(context:any) {
let filter:any = {};
let getValues = (selection:any, parentObj?:string[]) => {
//selection = labelSelection(selection);
selection.map((selection:any) => {
// Check if the parentObj is defined
if(parentObj)
// Merge the two objects
_.merge(filter, [...parentObj, null].reduceRight((obj, next) => {
if(next === null) return ({[selection.name?.value]: 1});
return ({[next]: obj});
}, {}));
// Check for a nested selection set
if(selection.selectionSet?.selections !== undefined){
// If the selection has a selection set, then we need to recurse
if(!parentObj) getValues(selection.selectionSet?.selections, [selection.name.value]);
// If the selection is nested
else getValues(selection.selectionSet?.selections, [...parentObj, selection.name.value]);
}
});
}
// Start the recursive function
getValues(context.operation.selectionSet.selections);
return filter;
}
输入
{
role(id: "61f1ccc79623d445bd2f677f") {
name
users {
user_name
_id
permissions {
roles
}
}
permissions
}
}
输出(JSON.stringify)
{
"role":{
"name":1,
"users":{
"user_name":1,
"_id":1,
"permissions":{
"roles":1
}
},
"permissions":1
}
}