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 Fauna Shell quick start.
  1. Start Fauna Shell

    In a terminal, start Fauna Shell by running:

    fauna shell my_db
    Starting shell for database my_db
    Connected to https://db.fauna.com
    Type Ctrl+D or .exit to exit the shell
     
  2. 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 queries in Fauna 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"] }]
    })
  3. 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"))
        }})
      )
    )
  4. 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"))
        }})
      )
    )
  5. 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 Timestamp, with millisecond 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.

  6. 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" }}
    )
  7. 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.

  8. 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 command, 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.
  9. 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 command.

    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.

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!