01 Tutorials

Languages:

Introduction

In this tutorial, you’ll learn how to create a simple social graph using FaunaDB’s query language.

Social graphs are a common feature of many modern mobile, gaming, and web applications. The example we’ll build here can support features such as timelines, suggested followers, and Erdős numbers.

Getting Started

We recommend you first take a look at the FaunaDB Basics tutorial in order to get familiar with the basic concepts around FaunaDB.

To follow along you will need an admin API key. Create an admin key using the admin keys interface for your FaunaDB Cloud account, or if you are using FaunaDB Enterprise, use the configured root key for your cluster.

Create a Database


curl https://db.fauna.com/ \
    -u kqnPAi3Kj3ZgAAC0Hu51Ng2dtn0JcgP7Fb-Q_uzLGZE: \
    -d '{ "create_database": { "object": { "name": "my_app" } } }'

FaunaClient adminClient = FaunaClient.builder()
  .withSecret(adminKey)
  .build();

adminClient.query(CreateDatabase(Obj("name", Value("my_app"))));

var adminClient = new faunadb.Client({
  secret: adminKey
});

adminClient.query(q.CreateDatabase({ name: "my_app" }));

val adminClient = FaunaClient(secret = adminKey)

adminClient.query(CreateDatabase(Obj("name" -> "my_app")))

$admin_client = Fauna::Client.new(secret: adminKey)

$admin_client.query do
  create_database name: 'my_app'
end

var adminClient = new FaunaClient(secret: adminKey);

adminClient.Query(CreateDatabase(Obj("name", "my_app")));

adminClient = FaunaClient(secret=adminKey)

adminClient.query(q.create_database({"name": "my_app"}))

adminClient = f.NewFaunaClient(adminKey)

adminClient.Query(f.CreateDatabase(f.Obj{"name": "my_app"}))

let adminClient = FaunaDB.Client(secret: adminKey)

adminClient.query(CreateDatabase(Obj("name" => "my_app")))

HTTP/1.1 201 Created
{
  "resource": {
    "ref": { "@ref": "databases/my_app" },
    "class": { "@ref": "databases" },
    "ts": 1436375112141542,
    "name": "my_app"
  }
}

=> {
  "ref": { "@ref": "databases/my_app" },
  "class": { "@ref": "databases" },
  "ts": 1436375112141542,
  "name": "my_app"
}

=> {
  "ref": { "@ref": "databases/my_app" },
  "class": { "@ref": "databases" },
  "ts": 1436375112141542,
  "name": "my_app"
}

=> {
  "ref": { "@ref": "databases/my_app" },
  "class": { "@ref": "databases" },
  "ts": 1436375112141542,
  "name": "my_app"
}

=> {
  "ref": { "@ref": "databases/my_app" },
  "class": { "@ref": "databases" },
  "ts": 1436375112141542,
  "name": "my_app"
}

=> {
  "ref": { "@ref": "databases/my_app" },
  "class": { "@ref": "databases" },
  "ts": 1436375112141542,
  "name": "my_app"
}

=> {
  "ref": { "@ref": "databases/my_app" },
  "class": { "@ref": "databases" },
  "ts": 1436375112141542,
  "name": "my_app"
}

=> {
  "ref": { "@ref": "databases/my_app" },
  "class": { "@ref": "databases" },
  "ts": 1436375112141542,
  "name": "my_app"
}

=> {
  "ref": { "@ref": "databases/my_app" },
  "class": { "@ref": "databases" },
  "ts": 1436375112141542,
  "name": "my_app"
}

You’ll use the my_app database across all of the tutorials, so this query may return an error if the database already exists.

Getting Access to the Database

Create an initial server key for our database by using an admin key. The server key has unrestricted access to a single database.


curl https://db.fauna.com/ \
    -u kqnPAi3Kj3ZgAAC0Hu51Ng2dtn0JcgP7Fb-Q_uzLGZE: \
    -d '{
          "create_key": {
            "object": { "database": { "database": "my_app" }, "role": "server" }
          }
        }'

Value key = adminClient.query(
  CreateKey(
    Obj(
      "database", Database(Value("my_app")),
      "role", Value("server")
    ))).get(1, TimeUnit.SECONDS); // block thread until key gets created

String serverKey = key.get(Field.at("secret").to(Codec.STRING));

var serverKey;
adminClient.query(
  q.CreateKey(
    { database: q.Database("my_app"), role: "server" }))
  .then(function(key) { serverKey = key.secret; });

val keyF = adminClient.query(
  CreateKey(
    Obj("database" -> Database("my_app"), "role" -> "server")))

val key = Await.result(keyF, 1.second) // block thread until key gets created
val serverKey = key(Field("secret").to[String]).get

key = $admin_client.query do
  create_key database: database('my_app'), role: 'server'
end

serverKey = key[:secret]

var key = await adminClient.Query(
  CreateKey(
    Obj("database", Database("my_app"), "role", "server")));

var serverKey = key.Get(Field.At("secret").To(Codec.STRING));

key = adminClient.query(
  q.create_key(
    {"database": q.database("my_app"), "role": "server"}
  ))

serverKey = key["secret"]

var serverKey string

key, _ := adminClient.Query(
    f.CreateKey(
        f.Obj{"database": f.Database("my_app"), "role": "server"},
    ),
)

_ = key.At(f.ObjKey("secret")).Get(&serverKey)

adminClient.query(
    CreateKey(
        Obj(
            "database" => Database("my_app"),
            "role" => "server"
        )
    )
)

HTTP/1.1 201 Created
{
  "resource": {
    "ref": { "@ref": "keys/104979509692858368" },
    "class": { "@ref": "keys" },
    "ts": 1436375112199704,
    "database": { "@ref": "databases/my_app" },
    "role": "server",
    "secret": "kqnPAi4gDivAAAC0i5t-a9YCcRnljtU2SfMJ-kWSRYI",
    "hashed_secret": "$2a$05$.saZ/E/JSuEEkbIfTU/GN.7iKEBBpBGePtWe/mZyL/rmf4uXeZ5oi"
  }
}

=> {
  "ref": { "@ref": "keys/104979509692858368" },
  "class": { "@ref": "keys" },
  "ts": 1436375112199704,
  "database": { "@ref": "databases/my_app" },
  "role": "server",
  "secret": "kqnPAi4gDivAAAC0i5t-a9YCcRnljtU2SfMJ-kWSRYI",
  "hashed_secret": "$2a$05$.saZ/E/JSuEEkbIfTU/GN.7iKEBBpBGePtWe/mZyL/rmf4uXeZ5oi"
}

=> {
  "ref": { "@ref": "keys/104979509692858368" },
  "class": { "@ref": "keys" },
  "ts": 1436375112199704,
  "database": { "@ref": "databases/my_app" },
  "role": "server",
  "secret": "kqnPAi4gDivAAAC0i5t-a9YCcRnljtU2SfMJ-kWSRYI",
  "hashed_secret": "$2a$05$.saZ/E/JSuEEkbIfTU/GN.7iKEBBpBGePtWe/mZyL/rmf4uXeZ5oi"
}

=> {
  "ref": { "@ref": "keys/104979509692858368" },
  "class": { "@ref": "keys" },
  "ts": 1436375112199704,
  "database": { "@ref": "databases/my_app" },
  "role": "server",
  "secret": "kqnPAi4gDivAAAC0i5t-a9YCcRnljtU2SfMJ-kWSRYI",
  "hashed_secret": "$2a$05$.saZ/E/JSuEEkbIfTU/GN.7iKEBBpBGePtWe/mZyL/rmf4uXeZ5oi"
}

=> {
  "ref": { "@ref": "keys/104979509692858368" },
  "class": { "@ref": "keys" },
  "ts": 1436375112199704,
  "database": { "@ref": "databases/my_app" },
  "role": "server",
  "secret": "kqnPAi4gDivAAAC0i5t-a9YCcRnljtU2SfMJ-kWSRYI",
  "hashed_secret": "$2a$05$.saZ/E/JSuEEkbIfTU/GN.7iKEBBpBGePtWe/mZyL/rmf4uXeZ5oi"
}

=> {
  "ref": { "@ref": "keys/104979509692858368" },
  "class": { "@ref": "keys" },
  "ts": 1436375112199704,
  "database": { "@ref": "databases/my_app" },
  "role": "server",
  "secret": "kqnPAi4gDivAAAC0i5t-a9YCcRnljtU2SfMJ-kWSRYI",
  "hashed_secret": "$2a$05$.saZ/E/JSuEEkbIfTU/GN.7iKEBBpBGePtWe/mZyL/rmf4uXeZ5oi"
}

=> {
  "ref": { "@ref": "keys/104979509692858368" },
  "class": { "@ref": "keys" },
  "ts": 1436375112199704,
  "database": { "@ref": "databases/my_app" },
  "role": "server",
  "secret": "kqnPAi4gDivAAAC0i5t-a9YCcRnljtU2SfMJ-kWSRYI",
  "hashed_secret": "$2a$05$.saZ/E/JSuEEkbIfTU/GN.7iKEBBpBGePtWe/mZyL/rmf4uXeZ5oi"
}

=> {
  "ref": { "@ref": "keys/104979509692858368" },
  "class": { "@ref": "keys" },
  "ts": 1436375112199704,
  "database": { "@ref": "databases/my_app" },
  "role": "server",
  "secret": "kqnPAi4gDivAAAC0i5t-a9YCcRnljtU2SfMJ-kWSRYI",
  "hashed_secret": "$2a$05$.saZ/E/JSuEEkbIfTU/GN.7iKEBBpBGePtWe/mZyL/rmf4uXeZ5oi"
}

=> {
  "ref": { "@ref": "keys/104979509692858368" },
  "class": { "@ref": "keys" },
  "ts": 1436375112199704,
  "database": { "@ref": "databases/my_app" },
  "role": "server",
  "secret": "kqnPAi4gDivAAAC0i5t-a9YCcRnljtU2SfMJ-kWSRYI",
  "hashed_secret": "$2a$05$.saZ/E/JSuEEkbIfTU/GN.7iKEBBpBGePtWe/mZyL/rmf4uXeZ5oi"
}

Representing People

The first class we’ll create is the people class, it represents the users in our social graph. Users could be players in a game, subscribers to an author’s articles, or coworkers in a professional network.


curl https://db.fauna.com/ \
    -u kqnPAi4gDivAAAC0i5t-a9YCcRnljtU2SfMJ-kWSRYI: \
    -d '{ "create_class": { "object": { "name": "people" } } }'

FaunaClient client = FaunaClient.builder()
  .withSecret(serverKey)
  .build();

client.query(CreateClass(Obj("name", Value("people"))));

var client = new faunadb.Client({
  secret: serverKey
});

client.query(q.CreateClass({ name: "people" }));

val client = FaunaClient(secret = serverKey)

client.query(CreateClass(Obj("name" -> "people")))

$client = Fauna::Client.new(secret: serverKey)

$client.query do
  create_class name: 'people'
end

var client = new FaunaClient(secret: serverKey);

client.Query(CreateClass(Obj("name", "people")));

client = FaunaClient(secret=serverKey)

client.query(q.create_class({"name": "people"}))

client = f.NewFaunaClient(serverKey)

client.Query(f.CreateClass(f.Obj{"name": "people"}))

let client = FaunaDB.Client(secret: serverKey)

client.query(CreateClass(Obj("name" => "people")))

We’ll create an index on peoples’ names. The index will allow us to refer to people with names like “alice” rather than refs like classes/people/1. Also, let’s use the unique constraint to ensure that multiple users don’t have the same name.


curl https://db.fauna.com/ \
    -u kqnPAi4gDivAAAC0i5t-a9YCcRnljtU2SfMJ-kWSRYI: \
    -d '{
          "create_index": {
            "object": {
              "name": "people_by_name",
              "source": { "class": "people" },
              "terms": [ { "object": { "field": [ "data", "name" ] } } ],
              "unique": true
            }
          }
        }'

client.query(
  CreateIndex(
    Obj(
      "name", Value("people_by_name"),
      "source", Class(Value("people")),
      "terms", Arr(Obj("field", Arr(Value("data"), Value("name")))),
      "unique", Value(true)
    )));

client.query(
  q.CreateIndex(
    {
      name: "people_by_name",
      source: q.Class("people"),
      terms: [{ field: ["data", "name"] }],
      unique: true
    }));

client.query(
  CreateIndex(
    Obj(
      "name" -> "people_by_name",
      "source" -> Class("people"),
      "terms" -> Arr(Obj("field" -> Arr("data", "name"))),
      "unique" -> true
    )))

$client.query do
  create_index name: 'people_by_name',
               source: class_('people'),
               terms: [{ field: ['data', 'name'] }],
               unique: true
end

client.Query(
  CreateIndex(
    Obj(
      "name", "people_by_name",
      "source", Class("people"),
      "terms", Arr(Obj("field", Arr("data", "name"))),
      "unique", true
    )));

client.query(
  q.create_index(
    {
      "name": "people_by_name",
      "source": q.class_expr("people"),
      "terms": [{"field": ["data", "name"]}],
      "unique": True
    }
  ))

client.Query(
    f.CreateIndex(
        f.Obj{
            "name": "people_by_name",
            "source": f.Class("people"),
            "terms": f.Arr{f.Obj{"field": f.Arr{"data", "name"}}},
            "unique": true,
        },
    ),
)

client.query(
    CreateIndex(
        Obj(
            "name" => "people_by_name",
            "source" => Class("people"),
            "terms" => Arr(Obj("field" => Arr("data", "name"))),
            "unique" => true
        )
    )
)

Representing Relationships Between People

In this tutorial, we’ll call the connection between people a “relationship”. Relationships are directional: a person may follow another person without requiring the second person to reciprocate the relationship. We’ll call the first person a “follower” of the “followee”.


curl https://db.fauna.com/ \
    -u kqnPAi4gDivAAAC0i5t-a9YCcRnljtU2SfMJ-kWSRYI: \
    -d '{ "create_class": { "object": { "name": "relationships" } } }'

client.query(CreateClass(Obj("name", Value("relationships"))));

client.query(q.CreateClass({ name: "relationships" }));

client.query(CreateClass(Obj("name" -> "relationships")))

$client.query do
  create_class name: 'relationships'
end

client.Query(CreateClass(Obj("name", "relationships")));

client.query(q.create_class({"name": "relationships"}))

client.Query(f.CreateClass(f.Obj{"name": "relationships"}))

client.query(CreateClass(Obj("name" => "relationships")))

We’ll create an index on the relationships class which will be the core of our social graph. This index will allow us to easily answer questions such as “who follows person A?” or “who follows person A, but not person B?”


curl https://db.fauna.com/ \
    -u kqnPAi4gDivAAAC0i5t-a9YCcRnljtU2SfMJ-kWSRYI: \
    -d '{
          "create_index": {
            "object": {
              "name": "followers_by_followee",
              "source": { "class": "relationships" },
              "terms": [ { "object": { "field": [ "data", "followee" ] } } ],
              "values": [ { "object": { "field": [ "data", "follower" ] } } ]
            }
          }
        }'

client.query(
  CreateIndex(
    Obj(
      "name", Value("followers_by_followee"),
      "source", Class(Value("relationships")),
      "terms", Arr(Obj("field", Arr(Value("data"), Value("followee")))),
      "values", Arr(Obj("field", Arr(Value("data"), Value("follower"))))
    )));

client.query(
  q.CreateIndex(
    {
      name: "followers_by_followee",
      source: q.Class("relationships"),
      terms: [{ field: ["data", "followee"] }],
      values: [{ field: ["data", "follower"] }]
    }));

client.query(
  CreateIndex(
    Obj(
      "name" -> "followers_by_followee",
      "source" -> Class("relationships"),
      "terms" -> Arr(Obj("field" -> Arr("data", "followee"))),
      "values" -> Arr(Obj("field" -> Arr("data", "follower")))
    )))

$client.query do
  create_index name: 'followers_by_followee',
               source: class_('relationships'),
               terms: [{ field: ['data', 'followee'] }],
               values: [{ field: ['data', 'follower'] }]
end

client.Query(
  CreateIndex(
    Obj(
      "name", "followers_by_followee",
      "source", Class("relationships"),
      "terms", Arr(Obj("field", Arr("data", "followee"))),
      "values", Arr(Obj("field", Arr("data", "follower")))
    )));

client.query(
  q.create_index(
    {
      "name": "followers_by_followee",
      "source": q.class_expr("relationships"),
      "terms": [{"field": ["data", "followee"]}],
      "values": [{"field": ["data", "follower"]}]
    }
  ))

client.Query(
    f.CreateIndex(
        f.Obj{
            "name": "followers_by_followee",
            "source": f.Class("relationships"),
            "terms": f.Arr{f.Obj{"field": f.Arr{"data", "followee"}}},
            "values": f.Arr{f.Obj{"field": f.Arr{"data", "follower"}}},
        },
    ),
)

client.query(
    CreateIndex(
        Obj(
            "name" => "followers_by_followee",
            "source" => Class("relationships"),
            "terms" => Arr(Obj("field" => Arr("data", "followee"))),
            "values" => Arr(Obj("field" => Arr("data", "follower")))
        )
    )
)

Working with the Graph

Populating the Graph

Now that we have classes representing the people in our graph (classes/people) and their relationships to each other (classes/relationships), we can begin populating the graph.

First we’ll create four users: Alice, Bob, Carol, and Dave. Notice that we added an index on the field ["data", "name"] for the people class. We’ll be able to use that index to find the four people in our graph later.


curl https://db.fauna.com/ \
    -u kqnPAi4gDivAAAC0i5t-a9YCcRnljtU2SfMJ-kWSRYI: \
    -d '{
          "foreach": {
            "lambda": "name",
            "expr": {
              "create": { "class": "people" },
              "params": {
                "object": { "data": { "object": { "name": { "var": "name" } } } }
              }
            }
          },
          "collection": [ "alice", "bob", "carol", "dave" ]
        }'

client.query(
  Foreach(
    Arr(
      Value("alice"),
      Value("bob"),
      Value("carol"),
      Value("dave")
    ),
    Lambda(
      Value("name"),
      Create(
        Class(Value("people")),
        Obj("data", Obj("name", Var("name")))))));

client.query(
  q.Foreach(
    ["alice", "bob", "carol", "dave"],
    function(name) {
      return q.Create(q.Class("people"), { data: { name: name } });
    }));

client.query(
  Foreach(
    Arr("alice", "bob", "carol", "dave"),
    Lambda { name =>
      Create(
        Class("people"),
        Obj("data" -> Obj("name" -> name)))
    }))

$client.query do
  foreach ['alice', 'bob', 'carol', 'dave'] do |name|
    create(class_('people'), data: { name: name })
  end
end

client.Query(
  Foreach(
    Arr("alice", "bob", "carol", "dave"),
    name => Create(Class("people"), Obj("data", Obj("name", name)))));

client.query(
  q.foreach(
    lambda name: q.create(q.class_expr("people"), {"data": {"name": name}}),
    ["alice", "bob", "carol", "dave"]
  ))

client.Query(
    f.Foreach(
        f.Arr{"alice", "bob", "carol", "dave"},
        f.Lambda(
            "name",
            f.Create(
                f.Class("people"),
                f.Obj{"data": f.Obj{"name": f.Var("name")}},
            ),
        ),
    ),
)

client.query(
    Foreach(
        collection: Arr("alice", "bob", "carol", "dave"),
        in: { name in
            Create(
                at: Class("people"),
                Obj("data" => Obj("name" => name))
            )
        }
    )
)

HTTP/1.1 200 OK
{ "resource": [ "alice", "bob", "carol", "dave" ] }

=> [ "alice", "bob", "carol", "dave" ]

=> [ "alice", "bob", "carol", "dave" ]

=> [ "alice", "bob", "carol", "dave" ]

=> [ "alice", "bob", "carol", "dave" ]

=> [ "alice", "bob", "carol", "dave" ]

=> [ "alice", "bob", "carol", "dave" ]

=> [ "alice", "bob", "carol", "dave" ]

=> [ "alice", "bob", "carol", "dave" ]

In the following queries, you’ll see this query pattern:


curl https://db.fauna.com/ \
    -u kqnPAi4gDivAAAC0i5t-a9YCcRnljtU2SfMJ-kWSRYI: \
    -d '{
          "get": { "match": { "index": "people_by_name" }, "terms": "alice" }
        }'

client.query(
  Get(Match(Index(Value("people_by_name")), Value("alice"))));

client.query(q.Get(q.Match(q.Index("people_by_name"), "alice")));

client.query(Get(Match(Index("people_by_name"), "alice")))

$client.query do
  get match(index('people_by_name'), 'alice')
end

client.Query(Get(Match(Index("people_by_name"), "alice")));

client.query(q.get(q.match(q.index("people_by_name"), "alice")))

client.Query(
    f.Get(f.MatchTerm(f.Index("people_by_name"), "alice")),
)

client.query(
    Get(
        Match(
            index: Index("people_by_name"),
            terms: "alice"
        )
    )
)

This query uses the index people_by_name to find the first person with the name “alice”. We can be sure that the first person returned is the one we’re looking for because of the uniqueness constraint we set when we created the index.

The first relationship we’ll create is between Alice and Bob. We’ll create a relationship with Alice as the follower, and Bob as the followee–in plain English, this says “Alice follows Bob.”


curl https://db.fauna.com/ \
    -u kqnPAi4gDivAAAC0i5t-a9YCcRnljtU2SfMJ-kWSRYI: \
    -d '{
          "create": { "class": "relationships" },
          "params": {
            "object": {
              "data": {
                "object": {
                  "followee": {
                    "select": "ref",
                    "from": {
                      "get": {
                        "match": { "index": "people_by_name" },
                        "terms": "alice"
                      }
                    }
                  },
                  "follower": {
                    "select": "ref",
                    "from": {
                      "get": {
                        "match": { "index": "people_by_name" },
                        "terms": "bob"
                      }
                    }
                  }
                }
              }
            }
          }
        }'

client.query(
  Create(
    Class(Value("relationships")),
    Obj(
      "data", Obj(
        "followee", Select(
          Value("ref"),
          Get(Match(Index(Value("people_by_name")), Value("alice")))),
        "follower", Select(
          Value("ref"),
          Get(Match(Index(Value("people_by_name")), Value("bob"))))
      )
    )));

client.query(
  q.Create(
    q.Class("relationships"),
    {
      data: {
        followee: q.Select(
          "ref",
          q.Get(q.Match(q.Index("people_by_name"), "alice"))),
        follower: q.Select(
          "ref",
          q.Get(q.Match(q.Index("people_by_name"), "bob")))
      }
    }));

client.query(
  Create(
    Class("relationships"),
    Obj(
      "data" -> Obj(
        "followee" -> Select("ref", Get(Match(Index("people_by_name"), "alice"))),
        "follower" -> Select("ref", Get(Match(Index("people_by_name"), "bob")))
      )
    )))

$client.query do
  create class_('relationships'),
         data: {
           followee: select('ref', get(match(index('people_by_name'), 'alice'))),
           follower: select('ref', get(match(index('people_by_name'), 'bob')))
         }
end

client.Query(
  Create(
    Class("relationships"),
    Obj(
      "data", Obj(
        "followee", Select("ref", Get(Match(Index("people_by_name"), "alice"))),
        "follower", Select("ref", Get(Match(Index("people_by_name"), "bob")))
      )
    )));

client.query(
  q.create(
    q.class_expr("relationships"),
    {
      "data": {
        "followee": q.select(
          "ref",
          q.get(q.match(q.index("people_by_name"), "alice"))
        ),
        "follower": q.select(
          "ref",
          q.get(q.match(q.index("people_by_name"), "bob"))
        )
      }
    }
  ))

client.Query(
    f.Create(
        f.Class("relationships"),
        f.Obj{
            "data": f.Obj{
                "followee": f.Select(
                    "ref",
                    f.Get(
                        f.MatchTerm(f.Index("people_by_name"), "alice"),
                    ),
                ),
                "follower": f.Select(
                    "ref",
                    f.Get(f.MatchTerm(f.Index("people_by_name"), "bob")),
                ),
            },
        },
    ),
)

client.query(
    Create(
        at: Class("relationships"),
        Obj(
            "data" => Obj(
                "followee" => Select(
                    path: "ref",
                    from: Get(
                        Match(
                            index: Index("people_by_name"),
                            terms: "alice"
                        )
                    )
                ),
                "follower" => Select(
                    path: "ref",
                    from: Get(
                        Match(
                            index: Index("people_by_name"),
                            terms: "bob"
                        )
                    )
                )
            )
        )
    )
)

HTTP/1.1 201 Created
{
  "resource": {
    "ref": { "@ref": "classes/relationships/104979509693618791" },
    "class": { "@ref": "classes/relationships" },
    "ts": 1436375112257866,
    "data": {
      "followee": { "@ref": "classes/people/104979509694379214" },
      "follower": { "@ref": "classes/people/104979509695139637" }
    }
  }
}

=> {
  "ref": { "@ref": "classes/relationships/104979509693618791" },
  "class": { "@ref": "classes/relationships" },
  "ts": 1436375112257866,
  "data": {
    "followee": { "@ref": "classes/people/104979509694379214" },
    "follower": { "@ref": "classes/people/104979509695139637" }
  }
}

=> {
  "ref": { "@ref": "classes/relationships/104979509693618791" },
  "class": { "@ref": "classes/relationships" },
  "ts": 1436375112257866,
  "data": {
    "followee": { "@ref": "classes/people/104979509694379214" },
    "follower": { "@ref": "classes/people/104979509695139637" }
  }
}

=> {
  "ref": { "@ref": "classes/relationships/104979509693618791" },
  "class": { "@ref": "classes/relationships" },
  "ts": 1436375112257866,
  "data": {
    "followee": { "@ref": "classes/people/104979509694379214" },
    "follower": { "@ref": "classes/people/104979509695139637" }
  }
}

=> {
  "ref": { "@ref": "classes/relationships/104979509693618791" },
  "class": { "@ref": "classes/relationships" },
  "ts": 1436375112257866,
  "data": {
    "followee": { "@ref": "classes/people/104979509694379214" },
    "follower": { "@ref": "classes/people/104979509695139637" }
  }
}

=> {
  "ref": { "@ref": "classes/relationships/104979509693618791" },
  "class": { "@ref": "classes/relationships" },
  "ts": 1436375112257866,
  "data": {
    "followee": { "@ref": "classes/people/104979509694379214" },
    "follower": { "@ref": "classes/people/104979509695139637" }
  }
}

=> {
  "ref": { "@ref": "classes/relationships/104979509693618791" },
  "class": { "@ref": "classes/relationships" },
  "ts": 1436375112257866,
  "data": {
    "followee": { "@ref": "classes/people/104979509694379214" },
    "follower": { "@ref": "classes/people/104979509695139637" }
  }
}

=> {
  "ref": { "@ref": "classes/relationships/104979509693618791" },
  "class": { "@ref": "classes/relationships" },
  "ts": 1436375112257866,
  "data": {
    "followee": { "@ref": "classes/people/104979509694379214" },
    "follower": { "@ref": "classes/people/104979509695139637" }
  }
}

=> {
  "ref": { "@ref": "classes/relationships/104979509693618791" },
  "class": { "@ref": "classes/relationships" },
  "ts": 1436375112257866,
  "data": {
    "followee": { "@ref": "classes/people/104979509694379214" },
    "follower": { "@ref": "classes/people/104979509695139637" }
  }
}

Next, we’ll add a relationship in the other direction: Bob follows Alice.


curl https://db.fauna.com/ \
    -u kqnPAi4gDivAAAC0i5t-a9YCcRnljtU2SfMJ-kWSRYI: \
    -d '{
          "create": { "class": "relationships" },
          "params": {
            "object": {
              "data": {
                "object": {
                  "followee": {
                    "select": "ref",
                    "from": {
                      "get": {
                        "match": { "index": "people_by_name" },
                        "terms": "bob"
                      }
                    }
                  },
                  "follower": {
                    "select": "ref",
                    "from": {
                      "get": {
                        "match": { "index": "people_by_name" },
                        "terms": "alice"
                      }
                    }
                  }
                }
              }
            }
          }
        }'

client.query(
  Create(
    Class(Value("relationships")),
    Obj(
      "data", Obj(
        "followee", Select(
          Value("ref"),
          Get(Match(Index(Value("people_by_name")), Value("bob")))),
        "follower", Select(
          Value("ref"),
          Get(Match(Index(Value("people_by_name")), Value("alice"))))
      )
    )));

client.query(
  q.Create(
    q.Class("relationships"),
    {
      data: {
        followee: q.Select(
          "ref",
          q.Get(q.Match(q.Index("people_by_name"), "bob"))),
        follower: q.Select(
          "ref",
          q.Get(q.Match(q.Index("people_by_name"), "alice")))
      }
    }));

client.query(
  Create(
    Class("relationships"),
    Obj(
      "data" -> Obj(
        "followee" -> Select("ref", Get(Match(Index("people_by_name"), "bob"))),
        "follower" -> Select("ref", Get(Match(Index("people_by_name"), "alice")))
      )
    )))

$client.query do
  create class_('relationships'),
         data: {
           followee: select('ref', get(match(index('people_by_name'), 'bob'))),
           follower: select('ref', get(match(index('people_by_name'), 'alice')))
         }
end

client.Query(
  Create(
    Class("relationships"),
    Obj(
      "data", Obj(
        "followee", Select("ref", Get(Match(Index("people_by_name"), "bob"))),
        "follower", Select("ref", Get(Match(Index("people_by_name"), "alice")))
      )
    )));

client.query(
  q.create(
    q.class_expr("relationships"),
    {
      "data": {
        "followee": q.select(
          "ref",
          q.get(q.match(q.index("people_by_name"), "bob"))
        ),
        "follower": q.select(
          "ref",
          q.get(q.match(q.index("people_by_name"), "alice"))
        )
      }
    }
  ))

client.Query(
    f.Create(
        f.Class("relationships"),
        f.Obj{
            "data": f.Obj{
                "followee": f.Select(
                    "ref",
                    f.Get(f.MatchTerm(f.Index("people_by_name"), "bob")),
                ),
                "follower": f.Select(
                    "ref",
                    f.Get(
                        f.MatchTerm(f.Index("people_by_name"), "alice"),
                    ),
                ),
            },
        },
    ),
)

client.query(
    Create(
        at: Class("relationships"),
        Obj(
            "data" => Obj(
                "followee" => Select(
                    path: "ref",
                    from: Get(
                        Match(
                            index: Index("people_by_name"),
                            terms: "bob"
                        )
                    )
                ),
                "follower" => Select(
                    path: "ref",
                    from: Get(
                        Match(
                            index: Index("people_by_name"),
                            terms: "alice"
                        )
                    )
                )
            )
        )
    )
)

HTTP/1.1 201 Created
{
  "resource": {
    "ref": { "@ref": "classes/relationships/104979509695900060" },
    "class": { "@ref": "classes/relationships" },
    "ts": 1436375112316028,
    "data": {
      "followee": { "@ref": "classes/people/104979509695139637" },
      "follower": { "@ref": "classes/people/104979509694379214" }
    }
  }
}

=> {
  "ref": { "@ref": "classes/relationships/104979509695900060" },
  "class": { "@ref": "classes/relationships" },
  "ts": 1436375112316028,
  "data": {
    "followee": { "@ref": "classes/people/104979509695139637" },
    "follower": { "@ref": "classes/people/104979509694379214" }
  }
}

=> {
  "ref": { "@ref": "classes/relationships/104979509695900060" },
  "class": { "@ref": "classes/relationships" },
  "ts": 1436375112316028,
  "data": {
    "followee": { "@ref": "classes/people/104979509695139637" },
    "follower": { "@ref": "classes/people/104979509694379214" }
  }
}

=> {
  "ref": { "@ref": "classes/relationships/104979509695900060" },
  "class": { "@ref": "classes/relationships" },
  "ts": 1436375112316028,
  "data": {
    "followee": { "@ref": "classes/people/104979509695139637" },
    "follower": { "@ref": "classes/people/104979509694379214" }
  }
}

=> {
  "ref": { "@ref": "classes/relationships/104979509695900060" },
  "class": { "@ref": "classes/relationships" },
  "ts": 1436375112316028,
  "data": {
    "followee": { "@ref": "classes/people/104979509695139637" },
    "follower": { "@ref": "classes/people/104979509694379214" }
  }
}

=> {
  "ref": { "@ref": "classes/relationships/104979509695900060" },
  "class": { "@ref": "classes/relationships" },
  "ts": 1436375112316028,
  "data": {
    "followee": { "@ref": "classes/people/104979509695139637" },
    "follower": { "@ref": "classes/people/104979509694379214" }
  }
}

=> {
  "ref": { "@ref": "classes/relationships/104979509695900060" },
  "class": { "@ref": "classes/relationships" },
  "ts": 1436375112316028,
  "data": {
    "followee": { "@ref": "classes/people/104979509695139637" },
    "follower": { "@ref": "classes/people/104979509694379214" }
  }
}

=> {
  "ref": { "@ref": "classes/relationships/104979509695900060" },
  "class": { "@ref": "classes/relationships" },
  "ts": 1436375112316028,
  "data": {
    "followee": { "@ref": "classes/people/104979509695139637" },
    "follower": { "@ref": "classes/people/104979509694379214" }
  }
}

=> {
  "ref": { "@ref": "classes/relationships/104979509695900060" },
  "class": { "@ref": "classes/relationships" },
  "ts": 1436375112316028,
  "data": {
    "followee": { "@ref": "classes/people/104979509695139637" },
    "follower": { "@ref": "classes/people/104979509694379214" }
  }
}

We’ll now use the index people_by_name to find all users named either “alice” or “bob” and add a relationship to Carol–i.e. Carol follows both Alice and Bob.


curl https://db.fauna.com/ \
    -u kqnPAi4gDivAAAC0i5t-a9YCcRnljtU2SfMJ-kWSRYI: \
    -d '{
          "let": {
            "follower": {
              "select": "ref",
              "from": {
                "get": {
                  "match": { "index": "people_by_name" },
                  "terms": "carol"
                }
              }
            }
          },
          "in": {
            "foreach": {
              "lambda": "followee",
              "expr": {
                "create": { "class": "relationships" },
                "params": {
                  "object": {
                    "data": {
                      "object": {
                        "followee": { "var": "followee" },
                        "follower": { "var": "follower" }
                      }
                    }
                  }
                }
              }
            },
            "collection": {
              "paginate": {
                "union": [
                  {
                    "match": { "index": "people_by_name" },
                    "terms": "alice"
                  },
                  {
                    "match": { "index": "people_by_name" },
                    "terms": "bob"
                  }
                ]
              }
            }
          }
        }'

client.query(
  Let(
    "follower", Select(
      Value("ref"),
      Get(Match(Index(Value("people_by_name")), Value("carol"))))
  ).in(
    Foreach(
      Paginate(
          Union(
            Match(Index(Value("people_by_name")), Value("alice")),
            Match(Index(Value("people_by_name")), Value("bob")))),
      Lambda(
        Value("followee"),
        Create(
          Class(Value("relationships")),
          Obj(
            "data", Obj(
              "followee", Var("followee"),
              "follower", Var("follower")
            )
          ))))
  ));

client.query(
  q.Let(
    {
      follower: q.Select(
        "ref",
        q.Get(q.Match(q.Index("people_by_name"), "carol")))
    },
    q.Foreach(
      q.Paginate(
        q.Union(
          q.Match(q.Index("people_by_name"), "alice"),
          q.Match(q.Index("people_by_name"), "bob"))),
      function(followee) {
        return q.Create(
          q.Class("relationships"),
          {
            data: { followee: followee, follower: q.Var("follower") }
          });
      })));

client.query(
  Let {
    val follower = Select("ref", Get(Match(Index("people_by_name"), "carol")))
    Foreach(
      Paginate(
        Union(
          Match(Index("people_by_name"), "alice"),
          Match(Index("people_by_name"), "bob"))),
      Lambda { followee =>
        Create(
          Class("relationships"),
          Obj(
            "data" -> Obj("followee" -> followee, "follower" -> follower)
          ))
      })
  })

$client.query do
  let(follower: select('ref', get(match(index('people_by_name'), 'carol')))) do
    foreach paginate(union(match(index('people_by_name'), 'alice'),
                   match(index('people_by_name'), 'bob'))) do |followee|
      create(class_('relationships'),
             data: { followee: followee, follower: follower })
    end
  end
end

client.Query(
  Let(
    Obj(
      "follower", Select("ref", Get(Match(Index("people_by_name"), "carol")))
    ),
    Foreach(
      Paginate(
        Union(
          Match(Index("people_by_name"), "alice"),
          Match(Index("people_by_name"), "bob"))),
      followee => Create(
        Class("relationships"),
        Obj(
          "data", Obj("followee", followee, "follower", Var("follower"))
        )))));

client.query(
  q.let(
    {
      "follower": q.select(
        "ref",
        q.get(q.match(q.index("people_by_name"), "carol"))
      )
    },
    q.foreach(
      lambda followee: q.create(
        q.class_expr("relationships"),
        {
          "data": {"followee": followee, "follower": q.var("follower")}
        }
      ),
      q.paginate(
        q.union(
          q.match(q.index("people_by_name"), "alice"),
          q.match(q.index("people_by_name"), "bob")
        )
      )
    )
  ))

client.Query(
    f.Let(
        f.Obj{
            "follower": f.Select(
                "ref",
                f.Get(
                    f.MatchTerm(f.Index("people_by_name"), "carol"),
                ),
            ),
        },
        f.Foreach(
            f.Paginate(
                f.Union(
                    f.MatchTerm(
                        f.Index("people_by_name"),
                        "alice",
                    ),
                    f.MatchTerm(
                        f.Index("people_by_name"),
                        "bob",
                    ),
                ),
            ),
            f.Lambda(
                "followee",
                f.Create(
                    f.Class("relationships"),
                    f.Obj{
                        "data": f.Obj{
                            "followee": f.Var("followee"),
                            "follower": f.Var("follower"),
                        },
                    },
                ),
            ),
        ),
    ),
)

client.query(
    Let(
        bindings: [
        follower: Select(
            path: "ref",
            from: Get(
                Match(
                    index: Index("people_by_name"),
                    terms: "carol"
                )
            )
        )
        ],
        in: Foreach(
            collection: Paginate(
                Union(
                    Match(
                        index: Index("people_by_name"),
                        terms: "alice"
                    ),
                    Match(
                        index: Index("people_by_name"),
                        terms: "bob"
                    )
                )
            ),
            in: { followee in
                Create(
                    at: Class("relationships"),
                    Obj(
                        "data" => Obj(
                            "followee" => followee,
                            "follower" => follower
                        )
                    )
                )
            }
        )
    )
)

HTTP/1.1 200 OK
{
  "resource": {
    "data": [
      { "@ref": "classes/people/104979509695139637" },
      { "@ref": "classes/people/104979509694379214" }
    ]
  }
}

=> {
  "data": [
    { "@ref": "classes/people/104979509695139637" },
    { "@ref": "classes/people/104979509694379214" }
  ]
}

=> {
  "data": [
    { "@ref": "classes/people/104979509695139637" },
    { "@ref": "classes/people/104979509694379214" }
  ]
}

=> {
  "data": [
    { "@ref": "classes/people/104979509695139637" },
    { "@ref": "classes/people/104979509694379214" }
  ]
}

=> {
  "data": [
    { "@ref": "classes/people/104979509695139637" },
    { "@ref": "classes/people/104979509694379214" }
  ]
}

=> {
  "data": [
    { "@ref": "classes/people/104979509695139637" },
    { "@ref": "classes/people/104979509694379214" }
  ]
}

=> {
  "data": [
    { "@ref": "classes/people/104979509695139637" },
    { "@ref": "classes/people/104979509694379214" }
  ]
}

=> {
  "data": [
    { "@ref": "classes/people/104979509695139637" },
    { "@ref": "classes/people/104979509694379214" }
  ]
}

=> {
  "data": [
    { "@ref": "classes/people/104979509695139637" },
    { "@ref": "classes/people/104979509694379214" }
  ]
}

Finally, we’ll add a relationship meaning, “Dave follows Alice.”


curl https://db.fauna.com/ \
    -u kqnPAi4gDivAAAC0i5t-a9YCcRnljtU2SfMJ-kWSRYI: \
    -d '{
          "create": { "class": "relationships" },
          "params": {
            "object": {
              "data": {
                "object": {
                  "followee": {
                    "select": "ref",
                    "from": {
                      "get": {
                        "match": { "index": "people_by_name" },
                        "terms": "alice"
                      }
                    }
                  },
                  "follower": {
                    "select": "ref",
                    "from": {
                      "get": {
                        "match": { "index": "people_by_name" },
                        "terms": "dave"
                      }
                    }
                  }
                }
              }
            }
          }
        }'

client.query(
  Create(
    Class(Value("relationships")),
    Obj(
      "data", Obj(
        "followee", Select(
          Value("ref"),
          Get(Match(Index(Value("people_by_name")), Value("alice")))),
        "follower", Select(
          Value("ref"),
          Get(Match(Index(Value("people_by_name")), Value("dave"))))
      )
    )));

client.query(
  q.Create(
    q.Class("relationships"),
    {
      data: {
        followee: q.Select(
          "ref",
          q.Get(q.Match(q.Index("people_by_name"), "alice"))),
        follower: q.Select(
          "ref",
          q.Get(q.Match(q.Index("people_by_name"), "dave")))
      }
    }));

client.query(
  Create(
    Class("relationships"),
    Obj(
      "data" -> Obj(
        "followee" -> Select("ref", Get(Match(Index("people_by_name"), "alice"))),
        "follower" -> Select("ref", Get(Match(Index("people_by_name"), "dave")))
      )
    )))

$client.query do
  create class_('relationships'),
         data: {
           followee: select('ref', get(match(index('people_by_name'), 'alice'))),
           follower: select('ref', get(match(index('people_by_name'), 'dave')))
         }
end

client.Query(
  Create(
    Class("relationships"),
    Obj(
      "data", Obj(
        "followee", Select("ref", Get(Match(Index("people_by_name"), "alice"))),
        "follower", Select("ref", Get(Match(Index("people_by_name"), "dave")))
      )
    )));

client.query(
  q.create(
    q.class_expr("relationships"),
    {
      "data": {
        "followee": q.select(
          "ref",
          q.get(q.match(q.index("people_by_name"), "alice"))
        ),
        "follower": q.select(
          "ref",
          q.get(q.match(q.index("people_by_name"), "dave"))
        )
      }
    }
  ))

client.Query(
    f.Create(
        f.Class("relationships"),
        f.Obj{
            "data": f.Obj{
                "followee": f.Select(
                    "ref",
                    f.Get(
                        f.MatchTerm(f.Index("people_by_name"), "alice"),
                    ),
                ),
                "follower": f.Select(
                    "ref",
                    f.Get(
                        f.MatchTerm(f.Index("people_by_name"), "dave"),
                    ),
                ),
            },
        },
    ),
)

client.query(
    Create(
        at: Class("relationships"),
        Obj(
            "data" => Obj(
                "followee" => Select(
                    path: "ref",
                    from: Get(
                        Match(
                            index: Index("people_by_name"),
                            terms: "alice"
                        )
                    )
                ),
                "follower" => Select(
                    path: "ref",
                    from: Get(
                        Match(
                            index: Index("people_by_name"),
                            terms: "dave"
                        )
                    )
                )
            )
        )
    )
)

HTTP/1.1 201 Created
{
  "resource": {
    "ref": { "@ref": "classes/relationships/104979509696660483" },
    "class": { "@ref": "classes/relationships" },
    "ts": 1436375112374190,
    "data": {
      "followee": { "@ref": "classes/people/104979509694379214" },
      "follower": { "@ref": "classes/people/104979509697420906" }
    }
  }
}

=> {
  "ref": { "@ref": "classes/relationships/104979509696660483" },
  "class": { "@ref": "classes/relationships" },
  "ts": 1436375112374190,
  "data": {
    "followee": { "@ref": "classes/people/104979509694379214" },
    "follower": { "@ref": "classes/people/104979509697420906" }
  }
}

=> {
  "ref": { "@ref": "classes/relationships/104979509696660483" },
  "class": { "@ref": "classes/relationships" },
  "ts": 1436375112374190,
  "data": {
    "followee": { "@ref": "classes/people/104979509694379214" },
    "follower": { "@ref": "classes/people/104979509697420906" }
  }
}

=> {
  "ref": { "@ref": "classes/relationships/104979509696660483" },
  "class": { "@ref": "classes/relationships" },
  "ts": 1436375112374190,
  "data": {
    "followee": { "@ref": "classes/people/104979509694379214" },
    "follower": { "@ref": "classes/people/104979509697420906" }
  }
}

=> {
  "ref": { "@ref": "classes/relationships/104979509696660483" },
  "class": { "@ref": "classes/relationships" },
  "ts": 1436375112374190,
  "data": {
    "followee": { "@ref": "classes/people/104979509694379214" },
    "follower": { "@ref": "classes/people/104979509697420906" }
  }
}

=> {
  "ref": { "@ref": "classes/relationships/104979509696660483" },
  "class": { "@ref": "classes/relationships" },
  "ts": 1436375112374190,
  "data": {
    "followee": { "@ref": "classes/people/104979509694379214" },
    "follower": { "@ref": "classes/people/104979509697420906" }
  }
}

=> {
  "ref": { "@ref": "classes/relationships/104979509696660483" },
  "class": { "@ref": "classes/relationships" },
  "ts": 1436375112374190,
  "data": {
    "followee": { "@ref": "classes/people/104979509694379214" },
    "follower": { "@ref": "classes/people/104979509697420906" }
  }
}

=> {
  "ref": { "@ref": "classes/relationships/104979509696660483" },
  "class": { "@ref": "classes/relationships" },
  "ts": 1436375112374190,
  "data": {
    "followee": { "@ref": "classes/people/104979509694379214" },
    "follower": { "@ref": "classes/people/104979509697420906" }
  }
}

=> {
  "ref": { "@ref": "classes/relationships/104979509696660483" },
  "class": { "@ref": "classes/relationships" },
  "ts": 1436375112374190,
  "data": {
    "followee": { "@ref": "classes/people/104979509694379214" },
    "follower": { "@ref": "classes/people/104979509697420906" }
  }
}

Exploring the Graph

To review, we now have four users and relationships among them in our social graph:

  • Alice follows Bob
  • Bob follows Alice
  • Carol follows both Alice and Bob
  • Dave follows Alice.

Using our index followers_by_followee, we can now answer questions about these relationships.

To find a person’s follower list, we’ll first use a select query to extract the ref of each follower, like this:


curl https://db.fauna.com/ \
    -u kqnPAi4gDivAAAC0i5t-a9YCcRnljtU2SfMJ-kWSRYI: \
    -d '{
          "select": "ref",
          "from": {
            "get": {
              "match": { "index": "people_by_name" },
              "terms": "alice"
            }
          }
        }'

client.query(
  Select(
    Value("ref"),
    Get(Match(Index(Value("people_by_name")), Value("alice")))));

client.query(
  q.Select(
    "ref",
    q.Get(q.Match(q.Index("people_by_name"), "alice"))));

client.query(
  Select("ref", Get(Match(Index("people_by_name"), "alice"))))

$client.query do
  select 'ref', get(match(index('people_by_name'), 'alice'))
end

client.Query(
  Select("ref", Get(Match(Index("people_by_name"), "alice"))));

client.query(
  q.select(
    "ref",
    q.get(q.match(q.index("people_by_name"), "alice"))
  ))

client.Query(
    f.Select(
        "ref",
        f.Get(f.MatchTerm(f.Index("people_by_name"), "alice")),
    ),
)

client.query(
    Select(
        path: "ref",
        from: Get(
            Match(
                index: Index("people_by_name"),
                terms: "alice"
            )
        )
    )
)

We can use that ref as the term in a match on our relationship index. This will return all of the refs of Alice’s followers:


curl https://db.fauna.com/ \
    -u kqnPAi4gDivAAAC0i5t-a9YCcRnljtU2SfMJ-kWSRYI: \
    -d '{
          "paginate": {
            "match": { "index": "followers_by_followee" },
            "terms": {
              "select": "ref",
              "from": {
                "get": {
                  "match": { "index": "people_by_name" },
                  "terms": "alice"
                }
              }
            }
          }
        }'

client.query(
  Paginate(
      Match(
        Index(Value("followers_by_followee")),
        Select(
          Value("ref"),
          Get(
            Match(Index(Value("people_by_name")), Value("alice")))))));

client.query(
  q.Paginate(
    q.Match(
      q.Index("followers_by_followee"),
      q.Select(
        "ref",
        q.Get(q.Match(q.Index("people_by_name"), "alice"))))));

client.query(
  Paginate(
    Match(
      Index("followers_by_followee"),
      Select(
        "ref",
        Get(Match(Index("people_by_name"), "alice"))))))

$client.query do
  paginate match(index('followers_by_followee'),
                 select('ref',
                        get(match(index('people_by_name'), 'alice'))))
end

client.Query(
  Paginate(
    Match(
      Index("followers_by_followee"),
      Select(
        "ref",
        Get(Match(Index("people_by_name"), "alice"))))));

client.query(
  q.paginate(
    q.match(
      q.index("followers_by_followee"),
      q.select(
        "ref",
        q.get(q.match(q.index("people_by_name"), "alice"))
      )
    )
  ))

client.Query(
    f.Paginate(
        f.MatchTerm(
            f.Index("followers_by_followee"),
            f.Select(
                "ref",
                f.Get(
                    f.MatchTerm(
                        f.Index("people_by_name"),
                        "alice",
                    ),
                ),
            ),
        ),
    ),
)

client.query(
    Paginate(
        Match(
            index: Index("followers_by_followee"),
            terms: Select(
                path: "ref",
                from: Get(
                    Match(
                        index: Index("people_by_name"),
                        terms: "alice"
                    )
                )
            )
        )
    )
)

Our application might be able to interpret refs, but they’re hard for a human to comprehend, so we’ll map over the set of refs, get each person’s instance, and select their name out of the instance.


curl https://db.fauna.com/ \
    -u kqnPAi4gDivAAAC0i5t-a9YCcRnljtU2SfMJ-kWSRYI: \
    -d '{
          "map": {
            "lambda": "person",
            "expr": {
              "select": [ "data", "name" ],
              "from": { "get": { "var": "person" } }
            }
          },
          "collection": {
            "paginate": {
              "match": { "index": "people_by_name" },
              "terms": "alice"
            }
          }
        }'

client.query(
  Map(
    Paginate(
        Match(Index(Value("people_by_name")), Value("alice"))),
    Lambda(
      Value("person"),
      Select(
        Arr(Value("data"), Value("name")),
        Get(Var("person"))))));

client.query(
  q.Map(
    q.Paginate(q.Match(q.Index("people_by_name"), "alice")),
    function(person) {
      return q.Select(["data", "name"], q.Get(person));
    }));

client.query(
  Map(
    Paginate(Match(Index("people_by_name"), "alice")),
    Lambda { person =>
      Select(Arr("data", "name"), Get(person))
    }))

$client.query do
  map paginate(match(index('people_by_name'), 'alice')) do |person|
    select(['data', 'name'], get(person))
  end
end

client.Query(
  Map(
    Paginate(Match(Index("people_by_name"), "alice")),
    person => Select(Arr("data", "name"), Get(person))));

client.query(
  q.map_expr(
    lambda person: q.select(["data", "name"], q.get(person)),
    q.paginate(q.match(q.index("people_by_name"), "alice"))
  ))

client.Query(
    f.Map(
        f.Paginate(
            f.MatchTerm(f.Index("people_by_name"), "alice"),
        ),
        f.Lambda(
            "person",
            f.Select(
                f.Arr{"data", "name"},
                f.Get(f.Var("person")),
            ),
        ),
    ),
)

client.query(
    Map(
        collection: Paginate(
            Match(
                index: Index("people_by_name"),
                terms: "alice"
            )
        ),
        to: { person in
            Select(path: "data", "name", from: Get(person))
        }
    )
)

Putting it all together gives us a human-friendly list of the names of Alice’s followers:


curl https://db.fauna.com/ \
    -u kqnPAi4gDivAAAC0i5t-a9YCcRnljtU2SfMJ-kWSRYI: \
    -d '{
          "map": {
            "lambda": "person",
            "expr": {
              "select": [ "data", "name" ],
              "from": { "get": { "var": "person" } }
            }
          },
          "collection": {
            "paginate": {
              "match": { "index": "followers_by_followee" },
              "terms": {
                "select": "ref",
                "from": {
                  "get": {
                    "match": { "index": "people_by_name" },
                    "terms": "alice"
                  }
                }
              }
            }
          }
        }'

client.query(
  Map(
    Paginate(
        Match(
          Index(Value("followers_by_followee")),
          Select(
            Value("ref"),
            Get(
              Match(
                Index(Value("people_by_name")),
                Value("alice")))))),
    Lambda(
      Value("person"),
      Select(
        Arr(Value("data"), Value("name")),
        Get(Var("person"))))));

client.query(
  q.Map(
    q.Paginate(
      q.Match(
        q.Index("followers_by_followee"),
        q.Select(
          "ref",
          q.Get(q.Match(q.Index("people_by_name"), "alice"))))),
    function(person) {
      return q.Select(["data", "name"], q.Get(person));
    }));

client.query(
  Map(
    Paginate(
      Match(
        Index("followers_by_followee"),
        Select(
          "ref",
          Get(Match(Index("people_by_name"), "alice"))))),
    Lambda { person =>
      Select(Arr("data", "name"), Get(person))
    }))

$client.query do
  map paginate(match(index('followers_by_followee'),
                 select('ref',
                        get(match(index('people_by_name'), 'alice'))))) do |person|
    select(['data', 'name'], get(person))
  end
end

client.Query(
  Map(
    Paginate(
      Match(
        Index("followers_by_followee"),
        Select(
          "ref",
          Get(Match(Index("people_by_name"), "alice"))))),
    person => Select(Arr("data", "name"), Get(person))));

client.query(
  q.map_expr(
    lambda person: q.select(["data", "name"], q.get(person)),
    q.paginate(
      q.match(
        q.index("followers_by_followee"),
        q.select(
          "ref",
          q.get(q.match(q.index("people_by_name"), "alice"))
        )
      )
    )
  ))

client.Query(
    f.Map(
        f.Paginate(
            f.MatchTerm(
                f.Index("followers_by_followee"),
                f.Select(
                    "ref",
                    f.Get(
                        f.MatchTerm(
                            f.Index("people_by_name"),
                            "alice",
                        ),
                    ),
                ),
            ),
        ),
        f.Lambda(
            "person",
            f.Select(
                f.Arr{"data", "name"},
                f.Get(f.Var("person")),
            ),
        ),
    ),
)

client.query(
    Map(
        collection: Paginate(
            Match(
                index: Index("followers_by_followee"),
                terms: Select(
                    path: "ref",
                    from: Get(
                        Match(
                            index: Index("people_by_name"),
                            terms: "alice"
                        )
                    )
                )
            )
        ),
        to: { person in
            Select(path: "data", "name", from: Get(person))
        }
    )
)

HTTP/1.1 200 OK
{ "resource": { "data": [ "bob", "dave", "carol" ] } }

=> { "data": [ "bob", "dave", "carol" ] }

=> { "data": [ "bob", "dave", "carol" ] }

=> { "data": [ "bob", "dave", "carol" ] }

=> { "data": [ "bob", "dave", "carol" ] }

=> { "data": [ "bob", "dave", "carol" ] }

=> { "data": [ "bob", "dave", "carol" ] }

=> { "data": [ "bob", "dave", "carol" ] }

=> { "data": [ "bob", "dave", "carol" ] }

Now that we’ve seen how to list a single person’s followers, we can use that knowledge to ask questions about the connections between follower lists.

For example, the union of follower lists tells us who follows either person. Here, we ask who follows Alice or Bob:


curl https://db.fauna.com/ \
    -u kqnPAi4gDivAAAC0i5t-a9YCcRnljtU2SfMJ-kWSRYI: \
    -d '{
          "map": {
            "lambda": "person",
            "expr": {
              "select": [ "data", "name" ],
              "from": { "get": { "var": "person" } }
            }
          },
          "collection": {
            "paginate": {
              "union": [
                {
                  "match": { "index": "followers_by_followee" },
                  "terms": {
                    "select": "ref",
                    "from": {
                      "get": {
                        "match": { "index": "people_by_name" },
                        "terms": "alice"
                      }
                    }
                  }
                },
                {
                  "match": { "index": "followers_by_followee" },
                  "terms": {
                    "select": "ref",
                    "from": {
                      "get": {
                        "match": { "index": "people_by_name" },
                        "terms": "bob"
                      }
                    }
                  }
                }
              ]
            }
          }
        }'

client.query(
  Map(
    Paginate(
        Union(
          Match(
            Index(Value("followers_by_followee")),
            Select(
              Value("ref"),
              Get(
                Match(
                  Index(Value("people_by_name")),
                  Value("alice"))))),
          Match(
            Index(Value("followers_by_followee")),
            Select(
              Value("ref"),
              Get(
                Match(
                  Index(Value("people_by_name")),
                  Value("bob"))))))),
    Lambda(
      Value("person"),
      Select(
        Arr(Value("data"), Value("name")),
        Get(Var("person"))))));

client.query(
  q.Map(
    q.Paginate(
      q.Union(
        q.Match(
          q.Index("followers_by_followee"),
          q.Select(
            "ref",
            q.Get(q.Match(q.Index("people_by_name"), "alice")))),
        q.Match(
          q.Index("followers_by_followee"),
          q.Select(
            "ref",
            q.Get(q.Match(q.Index("people_by_name"), "bob")))))),
    function(person) {
      return q.Select(["data", "name"], q.Get(person));
    }));

client.query(
  Map(
    Paginate(
      Union(
        Match(
          Index("followers_by_followee"),
          Select(
            "ref",
            Get(Match(Index("people_by_name"), "alice")))),
        Match(
          Index("followers_by_followee"),
          Select(
            "ref",
            Get(Match(Index("people_by_name"), "bob")))))),
    Lambda { person =>
      Select(Arr("data", "name"), Get(person))
    }))

$client.query do
  map paginate(union(match(index('followers_by_followee'),
                       select('ref',
                              get(match(index('people_by_name'), 'alice')))),
                 match(index('followers_by_followee'),
                       select('ref',
                              get(match(index('people_by_name'), 'bob')))))) do |person|
    select(['data', 'name'], get(person))
  end
end

client.Query(
  Map(
    Paginate(
      Union(
        Match(
          Index("followers_by_followee"),
          Select(
            "ref",
            Get(Match(Index("people_by_name"), "alice")))),
        Match(
          Index("followers_by_followee"),
          Select(
            "ref",
            Get(Match(Index("people_by_name"), "bob")))))),
    person => Select(Arr("data", "name"), Get(person))));

client.query(
  q.map_expr(
    lambda person: q.select(["data", "name"], q.get(person)),
    q.paginate(
      q.union(
        q.match(
          q.index("followers_by_followee"),
          q.select(
            "ref",
            q.get(q.match(q.index("people_by_name"), "alice"))
          )
        ),
        q.match(
          q.index("followers_by_followee"),
          q.select(
            "ref",
            q.get(q.match(q.index("people_by_name"), "bob"))
          )
        )
      )
    )
  ))

client.Query(
    f.Map(
        f.Paginate(
            f.Union(
                f.MatchTerm(
                    f.Index("followers_by_followee"),
                    f.Select(
                        "ref",
                        f.Get(
                            f.MatchTerm(
                                f.Index("people_by_name"),
                                "alice",
                            ),
                        ),
                    ),
                ),
                f.MatchTerm(
                    f.Index("followers_by_followee"),
                    f.Select(
                        "ref",
                        f.Get(
                            f.MatchTerm(
                                f.Index("people_by_name"),
                                "bob",
                            ),
                        ),
                    ),
                ),
            ),
        ),
        f.Lambda(
            "person",
            f.Select(
                f.Arr{"data", "name"},
                f.Get(f.Var("person")),
            ),
        ),
    ),
)

client.query(
    Map(
        collection: Paginate(
            Union(
                Match(
                    index: Index("followers_by_followee"),
                    terms: Select(
                        path: "ref",
                        from: Get(
                            Match(
                                index: Index("people_by_name"),
                                terms: "alice"
                            )
                        )
                    )
                ),
                Match(
                    index: Index("followers_by_followee"),
                    terms: Select(
                        path: "ref",
                        from: Get(
                            Match(
                                index: Index("people_by_name"),
                                terms: "bob"
                            )
                        )
                    )
                )
            )
        ),
        to: { person in
            Select(path: "data", "name", from: Get(person))
        }
    )
)

HTTP/1.1 200 OK
{ "resource": { "data": [ "bob", "alice", "dave", "carol" ] } }

=> { "data": [ "bob", "alice", "dave", "carol" ] }

=> { "data": [ "bob", "alice", "dave", "carol" ] }

=> { "data": [ "bob", "alice", "dave", "carol" ] }

=> { "data": [ "bob", "alice", "dave", "carol" ] }

=> { "data": [ "bob", "alice", "dave", "carol" ] }

=> { "data": [ "bob", "alice", "dave", "carol" ] }

=> { "data": [ "bob", "alice", "dave", "carol" ] }

=> { "data": [ "bob", "alice", "dave", "carol" ] }

The intersection of follower lists finds common followers among people. This query asks who follows Alice and Bob:


curl https://db.fauna.com/ \
    -u kqnPAi4gDivAAAC0i5t-a9YCcRnljtU2SfMJ-kWSRYI: \
    -d '{
          "map": {
            "lambda": "person",
            "expr": {
              "select": [ "data", "name" ],
              "from": { "get": { "var": "person" } }
            }
          },
          "collection": {
            "paginate": {
              "intersection": [
                {
                  "match": { "index": "followers_by_followee" },
                  "terms": {
                    "select": "ref",
                    "from": {
                      "get": {
                        "match": { "index": "people_by_name" },
                        "terms": "alice"
                      }
                    }
                  }
                },
                {
                  "match": { "index": "followers_by_followee" },
                  "terms": {
                    "select": "ref",
                    "from": {
                      "get": {
                        "match": { "index": "people_by_name" },
                        "terms": "bob"
                      }
                    }
                  }
                }
              ]
            }
          }
        }'

client.query(
  Map(
    Paginate(
        Intersection(
          Match(
            Index(Value("followers_by_followee")),
            Select(
              Value("ref"),
              Get(
                Match(
                  Index(Value("people_by_name")),
                  Value("alice"))))),
          Match(
            Index(Value("followers_by_followee")),
            Select(
              Value("ref"),
              Get(
                Match(
                  Index(Value("people_by_name")),
                  Value("bob"))))))),
    Lambda(
      Value("person"),
      Select(
        Arr(Value("data"), Value("name")),
        Get(Var("person"))))));

client.query(
  q.Map(
    q.Paginate(
      q.Intersection(
        q.Match(
          q.Index("followers_by_followee"),
          q.Select(
            "ref",
            q.Get(q.Match(q.Index("people_by_name"), "alice")))),
        q.Match(
          q.Index("followers_by_followee"),
          q.Select(
            "ref",
            q.Get(q.Match(q.Index("people_by_name"), "bob")))))),
    function(person) {
      return q.Select(["data", "name"], q.Get(person));
    }));

client.query(
  Map(
    Paginate(
      Intersection(
        Match(
          Index("followers_by_followee"),
          Select(
            "ref",
            Get(Match(Index("people_by_name"), "alice")))),
        Match(
          Index("followers_by_followee"),
          Select(
            "ref",
            Get(Match(Index("people_by_name"), "bob")))))),
    Lambda { person =>
      Select(Arr("data", "name"), Get(person))
    }))

$client.query do
  map paginate(intersection(match(index('followers_by_followee'),
                              select('ref',
                                     get(match(index('people_by_name'), 'alice')))),
                        match(index('followers_by_followee'),
                              select('ref',
                                     get(match(index('people_by_name'), 'bob')))))) do |person|
    select(['data', 'name'], get(person))
  end
end

client.Query(
  Map(
    Paginate(
      Intersection(
        Match(
          Index("followers_by_followee"),
          Select(
            "ref",
            Get(Match(Index("people_by_name"), "alice")))),
        Match(
          Index("followers_by_followee"),
          Select(
            "ref",
            Get(Match(Index("people_by_name"), "bob")))))),
    person => Select(Arr("data", "name"), Get(person))));

client.query(
  q.map_expr(
    lambda person: q.select(["data", "name"], q.get(person)),
    q.paginate(
      q.intersection(
        q.match(
          q.index("followers_by_followee"),
          q.select(
            "ref",
            q.get(q.match(q.index("people_by_name"), "alice"))
          )
        ),
        q.match(
          q.index("followers_by_followee"),
          q.select(
            "ref",
            q.get(q.match(q.index("people_by_name"), "bob"))
          )
        )
      )
    )
  ))

client.Query(
    f.Map(
        f.Paginate(
            f.Intersection(
                f.MatchTerm(
                    f.Index("followers_by_followee"),
                    f.Select(
                        "ref",
                        f.Get(
                            f.MatchTerm(
                                f.Index("people_by_name"),
                                "alice",
                            ),
                        ),
                    ),
                ),
                f.MatchTerm(
                    f.Index("followers_by_followee"),
                    f.Select(
                        "ref",
                        f.Get(
                            f.MatchTerm(
                                f.Index("people_by_name"),
                                "bob",
                            ),
                        ),
                    ),
                ),
            ),
        ),
        f.Lambda(
            "person",
            f.Select(
                f.Arr{"data", "name"},
                f.Get(f.Var("person")),
            ),
        ),
    ),
)

client.query(
    Map(
        collection: Paginate(
            Intersection(
                Match(
                    index: Index("followers_by_followee"),
                    terms: Select(
                        path: "ref",
                        from: Get(
                            Match(
                                index: Index("people_by_name"),
                                terms: "alice"
                            )
                        )
                    )
                ),
                Match(
                    index: Index("followers_by_followee"),
                    terms: Select(
                        path: "ref",
                        from: Get(
                            Match(
                                index: Index("people_by_name"),
                                terms: "bob"
                            )
                        )
                    )
                )
            )
        ),
        to: { person in
            Select(path: "data", "name", from: Get(person))
        }
    )
)

HTTP/1.1 200 OK
{ "resource": { "data": [ "carol" ] } }

=> { "data": [ "carol" ] }

=> { "data": [ "carol" ] }

=> { "data": [ "carol" ] }

=> { "data": [ "carol" ] }

=> { "data": [ "carol" ] }

=> { "data": [ "carol" ] }

=> { "data": [ "carol" ] }

=> { "data": [ "carol" ] }

The difference of follower lists tells us who follows some people, but not others. This is how we ask for followers of Alice, but not Bob:


curl https://db.fauna.com/ \
    -u kqnPAi4gDivAAAC0i5t-a9YCcRnljtU2SfMJ-kWSRYI: \
    -d '{
          "map": {
            "lambda": "person",
            "expr": {
              "select": [ "data", "name" ],
              "from": { "get": { "var": "person" } }
            }
          },
          "collection": {
            "paginate": {
              "difference": [
                {
                  "match": { "index": "followers_by_followee" },
                  "terms": {
                    "select": "ref",
                    "from": {
                      "get": {
                        "match": { "index": "people_by_name" },
                        "terms": "alice"
                      }
                    }
                  }
                },
                {
                  "match": { "index": "followers_by_followee" },
                  "terms": {
                    "select": "ref",
                    "from": {
                      "get": {
                        "match": { "index": "people_by_name" },
                        "terms": "bob"
                      }
                    }
                  }
                }
              ]
            }
          }
        }'

client.query(
  Map(
    Paginate(
        Difference(
          Match(
            Index(Value("followers_by_followee")),
            Select(
              Value("ref"),
              Get(
                Match(
                  Index(Value("people_by_name")),
                  Value("alice"))))),
          Match(
            Index(Value("followers_by_followee")),
            Select(
              Value("ref"),
              Get(
                Match(
                  Index(Value("people_by_name")),
                  Value("bob"))))))),
    Lambda(
      Value("person"),
      Select(
        Arr(Value("data"), Value("name")),
        Get(Var("person"))))));

client.query(
  q.Map(
    q.Paginate(
      q.Difference(
        q.Match(
          q.Index("followers_by_followee"),
          q.Select(
            "ref",
            q.Get(q.Match(q.Index("people_by_name"), "alice")))),
        q.Match(
          q.Index("followers_by_followee"),
          q.Select(
            "ref",
            q.Get(q.Match(q.Index("people_by_name"), "bob")))))),
    function(person) {
      return q.Select(["data", "name"], q.Get(person));
    }));

client.query(
  Map(
    Paginate(
      Difference(
        Match(
          Index("followers_by_followee"),
          Select(
            "ref",
            Get(Match(Index("people_by_name"), "alice")))),
        Match(
          Index("followers_by_followee"),
          Select(
            "ref",
            Get(Match(Index("people_by_name"), "bob")))))),
    Lambda { person =>
      Select(Arr("data", "name"), Get(person))
    }))

$client.query do
  map paginate(difference(match(index('followers_by_followee'),
                            select('ref',
                                   get(match(index('people_by_name'), 'alice')))),
                      match(index('followers_by_followee'),
                            select('ref',
                                   get(match(index('people_by_name'), 'bob')))))) do |person|
    select(['data', 'name'], get(person))
  end
end

client.Query(
  Map(
    Paginate(
      Difference(
        Match(
          Index("followers_by_followee"),
          Select(
            "ref",
            Get(Match(Index("people_by_name"), "alice")))),
        Match(
          Index("followers_by_followee"),
          Select(
            "ref",
            Get(Match(Index("people_by_name"), "bob")))))),
    person => Select(Arr("data", "name"), Get(person))));

client.query(
  q.map_expr(
    lambda person: q.select(["data", "name"], q.get(person)),
    q.paginate(
      q.difference(
        q.match(
          q.index("followers_by_followee"),
          q.select(
            "ref",
            q.get(q.match(q.index("people_by_name"), "alice"))
          )
        ),
        q.match(
          q.index("followers_by_followee"),
          q.select(
            "ref",
            q.get(q.match(q.index("people_by_name"), "bob"))
          )
        )
      )
    )
  ))

client.Query(
    f.Map(
        f.Paginate(
            f.Difference(
                f.MatchTerm(
                    f.Index("followers_by_followee"),
                    f.Select(
                        "ref",
                        f.Get(
                            f.MatchTerm(
                                f.Index("people_by_name"),
                                "alice",
                            ),
                        ),
                    ),
                ),
                f.MatchTerm(
                    f.Index("followers_by_followee"),
                    f.Select(
                        "ref",
                        f.Get(
                            f.MatchTerm(
                                f.Index("people_by_name"),
                                "bob",
                            ),
                        ),
                    ),
                ),
            ),
        ),
        f.Lambda(
            "person",
            f.Select(
                f.Arr{"data", "name"},
                f.Get(f.Var("person")),
            ),
        ),
    ),
)

client.query(
    Map(
        collection: Paginate(
            Difference(
                Match(
                    index: Index("followers_by_followee"),
                    terms: Select(
                        path: "ref",
                        from: Get(
                            Match(
                                index: Index("people_by_name"),
                                terms: "alice"
                            )
                        )
                    )
                ),
                Match(
                    index: Index("followers_by_followee"),
                    terms: Select(
                        path: "ref",
                        from: Get(
                            Match(
                                index: Index("people_by_name"),
                                terms: "bob"
                            )
                        )
                    )
                )
            )
        ),
        to: { person in
            Select(path: "data", "name", from: Get(person))
        }
    )
)

HTTP/1.1 200 OK
{ "resource": { "data": [ "bob", "dave" ] } }

=> { "data": [ "bob", "dave" ] }

=> { "data": [ "bob", "dave" ] }

=> { "data": [ "bob", "dave" ] }

=> { "data": [ "bob", "dave" ] }

=> { "data": [ "bob", "dave" ] }

=> { "data": [ "bob", "dave" ] }

=> { "data": [ "bob", "dave" ] }

=> { "data": [ "bob", "dave" ] }

Further Reading

In this tutorial, we’ve modeled a simple social graph using FaunaDB’s classes and indexes. We’ve used the query language to ask questions about the connections in the graph that are useful building blocks of social features in mobile apps, games, and web applications.

Take a look at our other Tutorials, as well as the Developer Guide for other useful information.