FQL v4 will be decommissioned on June 30, 2025. Ensure that you complete your migration from FQL v4 to FQL v10 by that date.

Fauna accounts created after August 21, 2024 must use FQL v10. These accounts will not be able to run FQL v4 queries or access the v4 Dashboard.

For more details, see the v4 EOL announcement and migration guide. Contact support@fauna.com with any questions.

Temporality

In this tutorial, we explore Fauna’s temporal features.

Temporal queries show you exactly how data has changed over time. You can ask for instances that have been updated in the last few days, and even scope whole queries to a point in the past, querying a snapshot of the database before and after particular transactions were processed.

This tutorial assumes that you have completed the Dashboard quick start.

Step 1: Open the Fauna Dashboard

Log in to the Fauna Dashboard and click the database my_db in your list of databases.

Step 2: Open the Dashboard Shell

Click SHELL in the left sidebar to display the Dashboard Shell.

Step 3: Create a collection and 2 indexes

We need a collection to store our data, and 2 indexes that help us explore the data. For our temporal features exploration, we’ll create a collection of shapes, a collection index that makes it easy to review all of our shapes, and a term-based index that makes it easy to lookup a shape by its name. Run the following query in the shell:

CreateCollection({ name: "shapes" })
CreateIndex({ name: "all_shapes", source: Collection("shapes") })

+

CreateIndex({
  name: "shapes_by_name",
  source: Collection("shapes"),
  unique: true,
  terms: [{ field: ["data", "name"] }]
})

Step 4: Create the first set of shape documents

Now we can create some shapes. Each shape has a name and a color.

Foreach(
  [
    ["triangle", "yellow"],
    ["square", "green"],
    ["circle", "blue"]
  ],
  Lambda("shape",
    Create(Collection("shapes"), { data: {
      name: Select(0, Var("shape")),
      color: Select(1, Var("shape"))
    }})
  )
)

Step 5: Create a second set of shape documents

After a short delay, run the following query to create some additional shapes. The delay only needs to be a few seconds; the delay gives this second set of shapes a different creation timestamp.

Foreach(
  [
    ["pentagon", "black"],
    ["hexagon", "cyan"],
    ["octagon", "red"]
  ],
  Lambda("shape",
    Create(Collection("shapes"), { data: {
      name: Select(0, Var("shape")),
      color: Select(1, Var("shape"))
    }})
  )
)

Step 6: Review the shapes

With our shapes created, let’s query Fauna to see our shape documents, and access their creation timestamps.

Map(
  Paginate(
    Match(Index("all_shapes"))
  ),
  Lambda("X", Get(Var("X")))
)

You should see output similar to:

{ data:
   [ { ref: Ref(Collection("shapes"), "232990372366647808"),
       ts: 1558455784100000,
       data: { name: 'square', color: 'green' } },
     { ref: Ref(Collection("shapes"), "232990372366648832"),
       ts: 1558455784100000,
       data: { name: 'circle', color: 'blue' } },
     { ref: Ref(Collection("shapes"), "232990372366649856"),
       ts: 1558455784100000,
       data: { name: 'triangle', color: 'yellow' } },
     { ref: Ref(Collection("shapes"), "232990436517478912"),
       ts: 1558455845280000,
       data: { name: 'pentagon', color: 'black' } },
     { ref: Ref(Collection("shapes"), "232990436517479936"),
       ts: 1558455845280000,
       data: { name: 'octagon', color: 'red' } },
     { ref: Ref(Collection("shapes"), "232990436517480960"),
       ts: 1558455845280000,
       data: { name: 'hexagon', color: 'cyan' } } ] }

The ts field for each document is a Long, with microsecond resolution, that represents the most recent event that modified the document. Fauna versions documents: each modification stores a new copy of the updated document.

Notice that the timestamps for each group of shapes are the same. Fauna processes each transaction so that all write effects appear to occur at the same moment. The values you see in your output should be different than shown here.

Step 7: Change some data

Since Fauna’s temporal features let you see how data has changed over time, let’s make some changes. Suppose we no longer want the black pentagon, and the circle should have been white. We can make those changes with the following queries:

Delete(Ref(Collection("shapes"), "232990436517478912"))
Update(
  Select("ref", Get(Match(Index("shapes_by_name"), "circle"))),
  { data: { color: "white" }}
)

Step 8: Review the shapes

Let’s run the review query again, so that we can see the effect of our changes.

Map(
  Paginate(
    Match(Index("all_shapes"))
  ),
  Lambda("X", Get(Var("X")))
)

You should see output similar to:

{ data:
   [ { ref: Ref(Collection("shapes"), "232990372366647808"),
       ts: 1558455784100000,
       data: { name: 'square', color: 'green' } },
     { ref: Ref(Collection("shapes"), "232990372366648832"),
       ts: 1558456119830000,
       data: { name: 'circle', color: 'white' } },
     { ref: Ref(Collection("shapes"), "232990372366649856"),
       ts: 1558455784100000,
       data: { name: 'triangle', color: 'yellow' } },
     { ref: Ref(Collection("shapes"), "232990436517479936"),
       ts: 1558455845280000,
       data: { name: 'octagon', color: 'red' } },
     { ref: Ref(Collection("shapes"), "232990436517480960"),
       ts: 1558455845280000,
       data: { name: 'hexagon', color: 'cyan' } } ] }

Good! The black pentagon is no longer listed, and our circle shape is now white.

Step 9: Temporal feature #1: Snapshots

Fauna’s snapshot feature lets you query your database to see the state of your data at a particular point in time. To review a snapshot of our shapes after the first group was created, but before the second group was created, we wrap our review query in the At function, and use the smaller of the two timestamps.

The timestamp in the query below needs to be updated, since it reflects the time when this tutorial was written. Any timestamp between when you created the first group of shapes and the creation of the second group of shapes would work for this demonstration. However, it is easiest to replace 1558455784100000 in the query below with the timestamp reported in your output for the square shape.
At(
  1558455784100000,
  Map(
    Paginate(
      Match(Index("all_shapes"))
    ),
    Lambda("X", Get(Var("X")))
  )
)

You should see output similar to:

{ data:
   [ { ref: Ref(Collection("shapes"), "232990372366647808"),
       ts: 1558455784100000,
       data: { name: 'square', color: 'green' } },
     { ref: Ref(Collection("shapes"), "232990372366648832"),
       ts: 1558455784100000,
       data: { name: 'circle', color: 'blue' } },
     { ref: Ref(Collection("shapes"), "232990372366649856"),
       ts: 1558455784100000,
       data: { name: 'triangle', color: 'yellow' } } ] }

Only the first group of shapes is returned, since at the timestamp we specified, the second group had not been created, nor had the circle been updated to be white.

At is inclusive, which means that any documents created before, and up to and including the specified timestamp are evaluated in the provided query expression.

Step 10: Temporal feature #2: Events

Fauna never changes a stored document. Any updates you make creates a new copy of the document containing the changes, and the original document remains unchanged. That means, for the circle shape, Fauna has two copies of the circle shape, one when it was blue, and the other when it was white.

We can see the history of a document by using the Events function.

Paginate(
  Events(
    Select(
      "ref",
      Get(Match(Index("shapes_by_name"), "circle"))
    )
  )
)

You should see output similar to:

{ data:
   [ { ts: 1558455784100000,
       action: 'create',
       instance: Ref(Collection("shapes"), "232990372366648832"),
       data: { name: 'circle', color: 'blue' } },
     { ts: 1558456119830000,
       action: 'update',
       instance: Ref(Collection("shapes"), "232990372366648832"),
       data: { color: 'white' } } ] }

To explain the query, it is helpful to start at the right:

  • Get(Match(Index("shapes_by_name"), "circle")) retrieves the circle shape.

  • Select("ref", Get(…​)) extracts the ref field from the circle’s document.

  • Events(Select(…​)) retrieves the set of events for the specified reference.

  • Paginate(Events(…​)) returns the materialized set of events.

Events can provide the set of modifications for any kind of document within Fauna. For user documents, the actions include create, insert, remove, replace, update, and delete. For indexes and databases, the actions include add and remove.

Since documents are stored immutably within Fauna, events are stored as snapshots of the associated document. Directly manipulating document history, using the Insert and Remove functions, does not currently affect subsequent event snapshots.

Conclusion

In this tutorial, we have modeled some simple documents with differing timestamps, and learned how to query the state of the database at a snapshot, and how to see the history of a particular document.

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!