在 graphql 中按时间过滤(使用 faunaDB 服务)

filter by Time in graphql (using faunaDB service)

我的 graphQL 架构如下所示,

type Todo {
  name: String!
  created_at: Time
}

type Query {
  allTodos: [Todo!]!
  todosByCreatedAtFlag(created_at: Time!): [Todo!]!
}

这个查询有效。

query {  
  todosByCreatedAtFlag(created_at: "2017-02-08T16:10:33Z") {
    data {
      _id
      name
      created_at
    }
  }
}

谁能指出我如何在 graphql 中创建大于(或小于)时间的查询(使用 faunaDB)。

不支持 GraphQL 范围查询(但是..他们来了!)

FaunaDB 不为其 GraphQL out-of-the-box 提供范围查询,我们正在研究这些功能。

..但有一个解决方法。

这并不意味着它不能进行范围查询,因为范围查询在 FQL 中受支持,并且您始终可以 'escape' 从 GraphQL 到 FQL 到通过编写用户定义函数 (UDF) 实现更高级的查询。

.. 使用解析器

通过在模式中使用 @resolver 关键字,您可以通过使用 FQL 在 FaunaDB 中编写用户定义函数来自己实现 GraphQL 查询。文档中有一些基本示例,我想您可能需要一些帮助,所以我会给您写一个简单的示例。

我添加了您的架构并添加了两个文档:

首先是我们的架构将使用解析器扩展:

type Todo {
  name: String!
  created_at: Time
}

type Query {
  allTodos: [Todo!]!
  todosByCreatedAtFlag(created_at: Time!): [Todo!]!
  todosByCreatedRange(before: Time, after:Time): [Todo!]! @resolver
}

这只是添加一个功能让我们实现:

如果我们通过 GraphQL 调用,它会向我们提供我们之前在屏幕截图中看到的完全中止消息,因为它尚未实现。但是我们可以看到GraphQL语句实际上调用了函数。

..UDF 实现

我们要做的第一件事是添加只是写一个名字的参数作为lambda的第一个参数:

如果您需要传递多个参数(我在模式中定义的解析器中这样做),它也需要一个数组:

我们将添加一个索引来支持我们的查询。值用于范围(以及 return 值和排序)。我们将添加 created_at 以覆盖它并添加 ref 因为我们需要 return 值来获取索引后面的实际文档。

然后我们可以从编写一个简单的函数开始(目前还行不通)

Query(
      Lambda(
        ["before", "after"],
        Paginate(
          Range(Match(Index("todosByCreatedAtRange")), Var("before"), Var("after"))
        )
      )
    )

并且可以通过 shell.

手动调用函数来测试它

这确实是 return 两个对象(范围包括在内)。 当然,这有一个问题,它 而不是 return GraphQL 期望的结构中的数据,所以我们会得到这些奇怪的错误:

我们现在可以做两件事,要么在我们的架构中定义一个适合这些 and/or 的类型,我们可以调整数据 returns。我们将执行后者并将我们的结果调整为预期的 [Todo!]!结果给你看。

第一步,映射结果。我们在这里唯一介绍的是 Map 和 Lambda。我们还没有做任何特别的事情,我们只是 return 参考而不是 ts 和参考作为例子。

Query(
  Lambda(
    ["before", "after"],
    Map(
      Paginate(
        Range(
          Match(Index("todosByCreatedAtRange")),
          Var("before"),
          Var("after")
        )
      ),
      Lambda(["created_at", "ref"], Var("ref"))
    )
  )
)

调用它确实表明该函数现在只有 returns 引用。

我们来获取实际文档。我知道 FQL 很冗长(并且有充分的理由,虽然它在未来应该会变得不那么冗长)所以我开始添加评论来澄清事情

Query(
  Lambda(
    ["before", "after"],
    Map(
      // This is just the query to get your range
      Paginate(
        Range(
          Match(Index("todosByCreatedAtRange")),
          Var("before"),
          Var("after")
        )
      ),
      
      // This is a function that will be executed on each result (with the help of Map)
      Lambda(["created_at", "ref"], 
        // We'll use Let to structure our queries ( allowing us to use varaibles )
        Let({ 
          todo: Get(Var("ref")) 
        }, 
        // And then we return something
        Var("todo")))
    )
  )
)

我们的函数现在 returns 数据..哇哦!

我们仍然需要确保此数据符合 GraphQL 的预期,并且从架构中我们可以看到它需要 [Todo!]! (参见文档选项卡)和 Todo 看起来像(参见模式选项卡):

type Todo {
  _id: ID!
  _ts: Long!
  name: String!
  created_at: Time
}

您还可以从该文档选项卡中看到,'non-resolver' 查询自动更改为 return TodoPages。到目前为止我们写的函数实际上return页。

选项 1,更改架构并将其变成分页解析器。

我们可以通过向解析器添加 paginated: true 选项来解决这个问题。您将必须考虑将添加到解析器的额外参数,如 here 所述。我自己还没有尝试过,所以我不是 100% 确定它会如何工作。分页解析的优点是您可以立即在 GraphQL 端点中利用合理的分页。

方案二,转成non-paginated结果。

分页结果如下所示: { 数据: [ 文档 1, 文档 2, .. ], 前: ... 后: .. }

结果不接受页面,而是一个数组,所以我将更改它并检索数据字段:

我们得到了结果。

完整的查询如下所示:

Query(
  Lambda(
    ["before", "after"],
    Select(
      ["data"],
      Map(
        Paginate(
          Range(
            Match(Index("todosByCreatedAtRange")),
            Var("before"),
            Var("after")
          )
        ),
        Lambda(
          ["created_at", "ref"],
          Let({ todo: Get(Var("ref")) }, Var("todo"))
        )
      )
    )
  )
)

免责声明

一旦你习惯了,分页也成为你的责任(例如传递一个额外的参数)。您不能再像往常一样通过在 GraphQL 主体中请求关系来获取开箱即用的关系。

关于 UDF 的好处和 GraphQL/FQL

的混合的一些话

在您回避 FQL 之前(是的,我们确实必须添加范围查询并正在努力),这里有一些关于 UDF 方法的一般解释,以及为什么无论如何都要考虑它。

你会在某个时刻遇到在 GraphQL 中不可能的事情(复杂的条件离子交易,例如更新文档并仅在前一次更新产生的某些条件为真时更新此其他文档)。使用其他 GraphQL 实现的用户通常通过编写无服务器函数来解决这个问题,以防您必须实现高级逻辑或事务。

FaunaDB 对此的回答是使用他们的用户定义函数 (UDF)。这不是一个无服务器函数,它是一个用 FQL 实现的 FaunaDB 函数,乍一看可能很麻烦,但重要的是要意识到它为您提供了与 FaunaDB 提供的相同的好处(multi-region/strong consistency/scalability/free-tier/pay-as-you-go)。