FSL function schema
Learn: User-defined functions (UDFs) |
---|
This page covers the FSL syntax for function schemas. For an overview, see user-defined function (UDF). |
An FSL function schema defines a user-defined function (UDF). A user-defined function (UDF) is a set of one or more FQL statements stored as a reusable resource in a Fauna database. Like a stored procedure in SQL, a UDF can accept parameters, perform operations, and return results.
@role("server-readonly")
// Defines the `getCustomerName` UDF.
function getCustomerName(customerId: ID): String {
// Find the customer document by ID.
let customer = Customer.byId(customerId)
// Return the customer's name field.
customer!.name
}
You can create and manage schema using any of the following:
Fauna stores each function schema as an FQL document in the
Function
system
collection.
FSL syntax
[@role("<roleName>")]
[@alias(<aliasId>]
function <functionName> (<parameter>: <parameterType>): <returnType> {
<functionBody>
}
Annotations
This is the role to use when the UDF is called. This is typically used for privilege escalation when current privileges would otherwise be too restrictive. A function must declare a built-in role to be able to view logs.
The role can be set only by users with a privileged role, such as admin
,
server
, or a user-defined role that grants write privilege for
Functions.
Use @role
carefully. Setting the role privilege gives the function
permission to create, change, and remove documents when invoked by calling the
function. A UDF can change a role to change function privileges.
Examples
Basic example
You create and manage a UDF as an FSL function schema:
function getOrCreateCart(id) {
// Find the customer by ID, using the ! operator to
// assert that the customer exists.
// If the customer does not exist, fauna will throw a
// document_not_found error.
let customer = Customer.byId(id)!
if (customer!.cart == null) {
// Create a cart if the customer does not have one.
Order.create({
status: 'cart',
customer: Customer.byId(id),
createdAt: Time.now(),
payment: {}
})
} else {
// Return the cart if it already exists.
customer!.cart
}
}
You save and manage function schema using the
Fauna Dashboard or the
Fauna CLI's
fauna schema push
command.
Once saved in a database, you can call the UDF in FQL queries against the database:
// Call the `getOrCreateCart()` UDF with a
// customer id of `111`.
getOrCreateCart(111)
Type checking
You can explicitly type a UDF’s arguments and return value:
// The `x` argument must be a `Number`.
// The function returns a `Number` value.
function myFunction(x: Number): Number {
x + 2
}
Multiple statements
A UDF can contain multiple statements and expressions:
function calculateOrderTotal(order) {
// Calculate the subtotal by summing up the prices of all items.
let subtotal = order.items.fold(0, (sum, orderItem) => {
let orderItem: Any = orderItem
if (orderItem.product != null) {
sum + orderItem.product.price * orderItem.quantity
} else {
sum
}
})
// Calculate the tax based on the subtotal.
let tax = subtotal * 0.1
// Return the final total including the tax.
subtotal + tax
}
Composability
UDFs are composable, allowing you to combine multiple UDFs.
For example, you can define a UDF:
// Defines the `applyDiscount()` UDF.
function applyDiscount(total, discountPercent) {
total * (1 - discountPercent / 100)
}
And call the UDF in another UDF definition:
// Defines the `calculateFinalPrice()` UDF.
function calculateFinalPrice(order, discountPercent) {
let order: Any = order
// Calls the `calculateOrderTotal()` UDF.
let total = calculateOrderTotal(order)
// Calls the `applyDiscount()` UDF.
applyDiscount(total, discountPercent)
}
Error handling
Use reference:fql-api/globals/abort.adoc to raise an Abort error from a UDF:
function validateOrderStatusTransition(oldStatus, newStatus) {
if (oldStatus == "cart" && newStatus != "processing") {
// The order can only transition from cart to processing.
abort("Invalid status transition.")
} else if (oldStatus == "processing" && newStatus != "shipped") {
// The order can only transition from processing to shipped.
abort("Invalid status transition.")
} else if (oldStatus == "shipped" && newStatus != "delivered") {
// The order can only transition from shipped to delivered.
abort("Invalid status transition.")
}
}
Runtime privileges
By default, UDFs run with the privileges of the calling query’s authentication secret.
When you define a UDF, you can include an optional @role annotation. If provided, the UDF runs using the role’s privileges, regardless of the secret used to call it:
// Defines the `inventory()` function.
// Runs with the built-in `server-readonly` role's privileges.
@role("server")
function inventory(name) {
Product.byName(name) {
name,
description,
stock
}
}
Pass a collection as an argument
The following example passes a collection name as an argument. Use
Collection()
to
dynamically specify collection names in a query:
// Accepts a collection name as an argument.
function getPriceLowtoHigh(collection) {
// Uses `Collection()` to dynamically specify
// the collection name.
Collection(collection).sortedByPriceLowToHigh() {
price,
name,
description
}
}
The following query calls the function:
// Calls the `getPriceLowtoHigh()` UDF with
// a `Product` collection argument.
getPriceLowtoHigh("Product")
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!