Check out v4 of the Fauna CLI

v4 of the Fauna CLI is now in beta.

The new version introduces enhancements to the developer experience, including an improved authentication workflow. To get started, check out the CLI v4 quick start.

FSL collection schema: Computed field definitions

Learn: Computed fields

A computed field dynamically derives the value of a field using an anonymous, read-only function. The function is evaluated when a document is read. The function is passed one argument: the document to read.

The function returns the computed field’s value. Fauna doesn’t persistently store a computed field’s value. The value is computed on each read.

You define computed fields as part of an FSL collection schema. Computed field definitions are part of a collection’s document type definition.

collection Customer {
  ...
  // Computed field.
  // A document field that derives its value from a
  // user-defined, read-only FQL function that runs on every read.
  compute InventoryValue: Number = (.stock * .price)
  ...
}

You can create and manage schema using any of the following:

Fauna stores each collection schema as an FQL document in the Collection system collection. The Collection document’s computed_fields field contains FQL versions of the collection’s computed field definitions.

FSL syntax

compute <fieldName>[: <type>] <functionBody>

Properties

Parameter Type Required Description

fieldName

String

Yes

Computed field name. The fieldName can be the same as a persisted field name. An optional type can be declared for the function.

functionBody

Function

Yes

The function body is an anonymous function that is evaluated when a document is read. An optional type can also be provided as part of the function signature.

The anonymous function takes a source Document as input, operates on the document values, and returns a computed value.

Computed fields with defined anonymous functions can transform existing fields, provide convenient access to complex relations, and index over derived data.

The anonymous function return value is the value of the computed field.

Discussion

The computed_fields field defines document functions that evaluate a field when the document is read using the provided anonymous function. The anonymous function can:

  • read

  • cause other computed fields to be evaluated

  • call user-defined functions (UDFs).

A computed field anonymous function has the restriction that it can’t write to a document. Trying to write to a computed_fields field returns an error.

Computed fields take precedence over persisted fields, and persisted fields can be referenced as children of the .data field.

Computed fields and indexes

Computed fields can be indexed and when indexed, the value that is indexed is the value of the computed field computed at the time the index is built. Because of this, indexed computed fields have more restrictions to prevent changes to the value of the field that are hard to detect. These restrictions are enforced by the type system when changes are made to computed fields or index definitions, preventing such changes. They are also enforced at runtime when they cause evaluation of a computed field to fail during the index build, which results in a null term or value.

The following actions cause an index to be rebuilt:

  • Changes to the definition of an indexed computed field.

  • Removing the computed field. The new index treats the computed field like a persisted field.

When a new index entry is added or an existing entry is updated, the anonymous function computes the value at that time instead of every time the value is needed.

Computed fields can be indexed and used as unique constraints subject to the following restrictions, which cause the computed field evaluation to fail at index time because of an invalid index definition:

  • Indexed computed fields can’t use computed fields in the body.

  • Indexed computed fields can’t call UDFs.

  • Indexed computed fields can’t read.

Failing evaluation causes the document to emit a null term or value for the computed field.

The following table summarizes computed field index behavior:

Problem Does the entry exist?

One or more values error, but terms are correct. Termless indexes count are always correct.

Yes

At least one term doesn’t error.

Yes
Terms that error are null.

All terms error.

No

If an indexed computed field depends on another computed field, changing the definition of the first computed field silently changes the definition of the second.

Adding, updating, renaming, or removing non-indexed computed fields is safe. Changing an indexed computed field definition triggers a rebuild of its covering indexes.

Computed functions and types

Given the following schema, types are inferred on the index functions for the collection. For example:

collection Customer {
  ...
  compute fullName: String = doc => "#{doc.firstName} #{doc.lastName}"

  index byFullName {
    terms [.fullName]
  }
  ...
}

In a query, the type in the index function is inferred, and the following query results in a type error:

Customer.byFullName(3) // Returns a type error

Examples

Basic example

The following Product collection schema contains:

  • A computed status field definition

  • A byStatus() index definition that indexes status as a term

collection Customer {
  ...
  // Defines `status` as a computed document field.
  // If the user's `points` field value is above `100`,
  // `status` is `gold`. Otherwise, `status` is `standard`.
  compute status: String = (
    doc => (
        if( doc.points > 100 ) {
            "gold"
        } else {
            "standard"
        }
    )
  )

  // Defines the `byStatus()` index.
  // Use the index to get `Customer` documents by
  // `status` value.
  index byStatus {
    terms [.status ]
  }
  ...
}

The Customer collection contains the following document:

{
  id: "111",
  coll: Customer,
  ts: Time("2099-06-25T12:50:43.722Z"),
  name: "Alice Appleseed",
  email: "alice.appleseed@example.com",
  status: "gold",
  ...
  points: 120
}

Call the byStatus() index in a query:

Customer.byStatus( "gold")

The results include the document based on its status field value. Fauna computes the status field value at query time:

{
  data: [
    {
      id: "111",
      coll: Customer,
      ts: Time("2099-06-25T12:50:43.722Z"),
      status: "gold",
      name: "Alice Appleseed",
      email: "alice.appleseed@example.com",
      ...
      points: 120
    },
    ...
  ]
}

Use subqueries in a computed field

You can use subqueries in a computed field definition:

collection Customer {
  ...
  // Defines `orders` as a computed document field. `order`
  // contains an Array of the first five `Order` collection
  // documents returned by a `byCustomer()` index call. The
  // call passes the document as its only argument.
  compute orders:Array<Order> = (
    doc => { Order.byCustomer( doc ).take(5).toArray() }
  )
  ...
}

This approach is a useful alternative to directly embedding data from other documents into a field. However, you can’t index the computed field because it performs a read outside of the doc argument.

Define a computed field that validates a range of values

compute isAdult = (
  doc => (doc.age > 18 && doc.age <= 120)
)

Assign a type to a computed field

Assign a String type to a computed field:

collection Customer {
  ...
  compute status: String = doc => {
    if (doc.points > 100) {
      "gold"
    } else {
      "standard"
    }
  }
  ...
}

If the optional type is not provided, the type checker assigns the enumeration of the possible computed values, which are gold and standard.

Use a computed field as an index term

Convert a user name to uppercase and get the first letter of the name to use as a computed field for indexing on the first letter of user names:

collection Customer {
  ...
  compute nameInitial = (
    doc => { doc.firstName.toUpperCase().slice(0, 1) }
  )
  ...
}

Define a check constraint on a computed field

Validate that the result of the computed field is gold or standard:

collection Customer {
  ...
  compute status: String = (
    doc => (
        if( doc.points > 100 ) {
            "gold"
        } else {
            "standard"
        }
    )
  )

  check validStatus (.status == "gold" || .status == "standard")
  ...
}

Limitations

Runtime errors in computed fields that are indexed aren’t reported. Computed fields that result in runtime errors, including divide by zero, return null. If indexed, these computed fields are excluded from the index update.

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!