User-defined functions
User-defined functions (UDF) are Fauna Query Language
Lambda
functions, and they can be
exposed through the GraphQL API by using the
@resolver
directive at fields
in the Query and Mutation types. This directive has no effect if placed
elsewhere.
The UDF must accept an array of arguments, the same number and order as the associated field in the GraphQL schema. While an FQL function can accept a single argument as a scalar value, the GraphQL API always passes arguments, even a single argument, as an array. There is no association between the arguments' names in the GraphQL schema and the arguments' names in the UDF definition. When a UDF field is queried, the GraphQL API simply calls the underlying UDF with an array of the given arguments.
The UDF result type must be a GraphQL-compatible type. If an object is returned, an equivalent type must exist in the GraphQL schema to allow users to select which fields to return. Embedded types can be used to map return types that are not associated with any existing type.
UDFs with pagination support must return a database page. In addition to the function arguments, the API calls the UDF with three additional arguments appended the query’s arguments:
-
size
: the requested page size -
after
: a marker for the page of results following the current page, if available (null
if not). -
before
: a marker for the page of result before the current page, if available (null
if not).
A UDF’s implementation must account for these additional arguments and
call the appropriate form of the FQL
Paginate
function to return a
database page to the GraphQL API.
When a schema is imported that
involves the @resolver
directive, the import logic checks for an
existing UDF having the specified name. If no UDF exists with the
specified name, a "template" UDF is created for you. The template UDF
aborts the query with an error, since the actual logic to handle the
field has not been implemented.
Once a template UDF has been created, you can update the UDF with the actual functionality. For example:
Update(
Function("my_function"),
{
"body": Query(
Lambda(["param1", "param2"],
// your logic here
)
}
)
If you attempt to run
CreateFunction
using the
template UDF’s name, you receive an error because the UDF already
exists.
Examples
A UDF that returns a scalar type:
The following is a UDF, using Fauna Shell syntax, that returns a scalar type:
CreateFunction({
name: "say_hello",
body: Query(Lambda(["name"],
Concat(["Hello ", Var("name")])
))
})
A GraphQL schema that uses the UDF:
type Query {
sayHello(name: String!): String! @resolver(name: "say_hello")
}
With these in place, when you run the following query:
{
sayHello(name: "Jane")
}
The result should be:
{
"data": {
"sayHello": "Hello Jane"
}
}
A UDF that returns an embedded object
The following is a UDF, in Fauna Shell syntax, that returns an embedded object:
CreateFunction({
name: "sample_obj",
body: Query(Lambda([], {
time: Time("now"),
sample: true
}))
})
A GraphQL schema that uses the UDF:
type SampleObj @embedded {
time: Time!
sample: Boolean!
}
type Query {
sampleObj: SampleObj! @resolver(name: "sample_obj")
}
With these in place, when you run the following query:
{
sampleObj {
time
sample
}
}
The result should be:
{
"data": {
"sampleObj": {
"time": "2019-06-14T17:42:54.001987Z",
"sample": true
}
}
}
A UDF that returns a database page
The following is a UDF (along with a Collection and Index), in Fauna Shell syntax, that handles paginated results, so that repeated querying can retrieve all of the paginated results.
CreateCollection({
name: "users"
})
CreateIndex({
name: "vip_users",
source: Collection("users"),
terms: [{ field: [ "data", "vip" ] }]
})
CreateFunction({
name: "vip_users",
body: Query(Lambda(["size", "after", "before"],
Let(
{
match: Match(Index("vip_users"), true),
page: If(
Equals(Var("before"), null),
If(
Equals(Var("after"), null),
Paginate(Var("match"), { size: Var("size") }),
Paginate(Var("match"), { size: Var("size"), after: Var("after") })
),
Paginate(Var("match"), { size: Var("size"), before: Var("before") }),
)
},
Map(Var("page"), Lambda("ref", Get(Var("ref"))))
)
))
})
The function accepts the following parameters:
-
size
: The maximum number of items to include in a Page of results. -
after
: a marker representing the next page of results. If there are no more results,after
isnull
. If there are more results following the current page, theafter
points to the first item in the following page. -
before
: a marker representing the previous page of results. If there are no previous results,before
isnull
. If there are previous results before the current page, thebefore
points tosize
items before the current page, or the first available item if the current page does not start on a multiple ofsize
.
The function’s complexity comes from handling the null
cases for
after
and before
. In each case, a the result of a
Paginate
call is assigned to the
page
variable, which is used in the Map
function call (at the end) to fetch the details for the page of results.
A GraphQL schema that uses the UDF:
type User @collection(name: "users") {
username: String!
vip: Boolean!
}
type Query {
vips: [User!] @resolver(name: "vip_users", paginated: true)
}
With these in place, when you run the following query:
{
vips(
_size: 2
_cursor: "2DOB2DRyMjM1MTcwOTY5MDA0NTQwNDIzgWV1c2Vyc4FnY2xhc3Nlc4CAgIA="
) {
data {
username
}
after
before
}
}
Notice that our query includes _size
and _cursor
as arguments to the
resolver, and after
and before
in the results. The _cursor
parameter is a value acquired after performing the initial query (not
shown here), which provided the value in the after
or before
fields
in the results. A cursor includes both the position and direction, which
the GraphQL API uses to populate the after
and before
parameters
passed to the underlying FQL function.
The result should be:
{
"data": {
"vips": {
"data": [
{ "username": "Mary" },
{ "username": "Ted" }
],
"after": null,
"before": "2DKB2DRyMjM1MTcwOTY5MDA0NTQwNDIzgWV1c2Vyc4FnY2xhc3Nlc4CAgIA="
}
}
}
From the results, we can see that after
is null
, which means that
there are no more following pages of results, but that before
is
defined. A subsequent query can pass the value of before
as the
_cursor
argument to receive the page of results prior to the current
results.
Is this article helpful?
Tell Fauna how the article can be improved:
Visit Fauna's forums
or email docs@fauna.com
Thank you for your feedback!