Bindings

Overview

Index bindings allow users to store a Lambda function as part of an index definition, and to run that function whenever a new index entry is added or an existing one is updated. It is much more efficient to compute a value once, when an index entry is created or updated, than to compute it every time that the value is needed.

Bindings must be pure Lambda functions: they must not create side effects, such as any additional reads or writes. They are provided with a copy of the document to be indexed and must operate on the document’s values.

+ Functions that cannot be used in bindings include:

The ts field can be used as a term or value for an index but should not be used in a binding because it is not known at the time index bindings are computed.

Example

To demonstrate, let’s create an index which implements a simple binding function and uses its result as an index term. For the purposes of this demonstration, let’s use the People collection (as created in the indexing tutorials). The goal is to create an index to support a query which looks for people whose age is greater than 100.

client.query(
  q.CreateIndex({
    name: 'people_by_age',
    source: {
      collection: q.Collection('People'),
      fields: {
        over_100: q.Query(q.Lambda('doc', q.GTE(q.Select(['data', 'age'], q.Var('doc')), 100))),
      },
    },
    terms: [{ binding: 'over_100' }],
  })
)
.then((ret) => console.log(ret))
.catch((err) => console.error(
  'Error: [%s] %s: %s',
  err.name,
  err.message,
  err.errors()[0].description,
))
{
  ref: Index("people_by_age"),
  ts: 1644528504640000,
  active: true,
  serialized: true,
  name: 'people_by_age',
  source: {
    collection: Collection("People"),
    fields: {
      over_100: Query(Lambda("doc", GTE(Select(["data", "age"], Var("doc")), 100)))
    }
  },
  terms: [ { binding: 'over_100' } ],
  partitions: 1
}
Query metrics:
  •    bytesIn:   283

  •   bytesOut:   444

  • computeOps:     1

  •    readOps:     0

  •   writeOps:     8

  •  readBytes: 2,438

  • writeBytes: 1,335

  •  queryTime:  86ms

  •    retries:     0

The above example uses the GTE function to determine if the age field contains a number greater than or equal to 100, and returns true if it does.

We can now use the new index to support our desired access pattern. The following example uses the people_by_age index to look for people whose age is 100 or more.

client.query(
  q.Map(
    q.Paginate(q.Match(q.Index('people_by_age'), true)),
    q.Lambda('person', q.Get(q.Var('person')))
  )
)
.then((ret) => console.log(ret))
.catch((err) => console.error(
  'Error: [%s] %s: %s',
  err.name,
  err.message,
  err.errors()[0].description,
))
{
  data: [
    {
      ref: Ref(Collection("People"), "323244160540739072"),
      ts: 1644528503860000,
      data: {
        first: 'Alan',
        last: 'Turing',
        age: 107,
        degrees: [ 'BA', 'MA', 'MS', 'PhD' ],
        letter: 'B'
      }
    },
    {
      ref: Ref(Collection("People"), "323244160541786624"),
      ts: 1644528503860000,
      data: {
        first: 'Grace',
        last: 'Hopper',
        age: 119,
        degrees: [ 'BA', 'MA', 'PhD' ],
        letter: 'C'
      }
    }
  ]
}
Query metrics:
  •    bytesIn:  134

  •   bytesOut:  507

  • computeOps:    1

  •    readOps:    3

  •   writeOps:    0

  •  readBytes:  333

  • writeBytes:    0

  •  queryTime: 21ms

  •    retries:    0

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!