Function schema

Learn: User-defined functions (UDFs)

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
}

Syntax

[@role("<roleName>")]
[@alias(<aliasId>]
function <functionName> (<parameter>: <parameterType>): <returnType> {
  <functionBody>
}

Name

functionName String Required

Unique name of the UDF. The name is case insensitive and can’t be a reserved word.

Properties

Parameter Type Required Description

parameter

String

Yes

functionBody parameter

parameterType

String

Yes

parameter type

returnType

String

Yes

functionBody return type

functionBody

String

Yes

FQL block.

Annotations

roleName String

The optional @role annotation associates a Role named roleName with the function. The Role can be a user-defined role or one of the following built-in roles:

  • admin

  • server

  • server-readonly

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.

aliasId String or Identifier

The optional @alias annotation defines a second identifier for the function. The aliasId can be a String, "Foo", or an identifier, Foo.

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 schemas using the Fauna Dashboard or the Fauna CLI's 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!