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.
Properties
Parameter | Type | Required | Description |
---|---|---|---|
fieldName |
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 |
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 |
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 indexesstatus
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!