Working with data types

This section demonstrates techniques for working with Fauna data types in your application, specifically how to include values in queries and extract them from responses.

Include values in queries

For scalar values, plus arrays and objects, you can most often embed those values directly into queries, as static or variable values:

var bool = true
var bytes = new Uint8Array([0x1, 0x2, 0x3])
var nul = null
var num = 1234
var str = 'Hello World!'
var object = {
  first: 'Alan',
  last: 'Turing',
}
var array = ['A', 1, true, object]

client.query(
  {
    variables: {
      boolean: bool,
      null: nul,
      number: num,
      bytes: bytes,
      string: str,
      array: array,
      object: object,
    },
    literals: {
      boolean: false,
      null: null,
      number: 234,
      bytes: new Uint8Array([0x1, 0x2, 0x3]),
      string: 'FQL is great!',
      array: ['a', 'b', 'c'],
      object: { first: 'Alan', last: 'Turing' },
    },
    computed: {
      boolean: bool === false,
      null: (nul === null) ? null : 'error?',
      number: Math.max(12, 7, 48, 3),
      bytes: new Uint8Array([...bytes, new Uint8Array([0x1, 0x2, 0x3])]),
      string: ['FQL', 'is', 'powerful'].join(' '),
      array: 'FQL is functional!'.split(' '),
      object: Object.assign(object, { age: 107 }),
    },
  }
)
.then((ret) => console.log(ret))
.catch((err) => console.error(
  'Error: [%s] %s: %s',
  err.name,
  err.message,
  err.errors()[0].description,
))
{
  variables: {
    boolean: true,
    null: null,
    number: 1234,
    bytes: Bytes("AQID"),
    string: 'Hello World!',
    array: [ 'A', 1, true, { first: 'Alan', last: 'Turing', age: 107 } ],
    object: { first: 'Alan', last: 'Turing', age: 107 }
  },
  literals: {
    boolean: false,
    null: null,
    number: 234,
    bytes: Bytes("AQID"),
    string: 'FQL is great!',
    array: [ 'a', 'b', 'c' ],
    object: { first: 'Alan', last: 'Turing' }
  },
  computed: {
    boolean: false,
    null: null,
    number: 48,
    bytes: Bytes("AQIDAA=="),
    string: 'FQL is powerful',
    array: [ 'FQL', 'is', 'functional!' ],
    object: { first: 'Alan', last: 'Turing', age: 107 }
  }
}
Query metrics:
  •    bytesIn: 676

  •   bytesOut: 601

  • computeOps:   1

  •    readOps:   0

  •   writeOps:   0

  •  readBytes:   0

  • writeBytes:   0

  •  queryTime: 0ms

  •    retries:   0

Large integers

Some host languages, such as JavaScript, do not support 64-bit integers. If the number you wish to include in a query is larger than the precision of your host language, express it as a string:

var maxInt = Number.MAX_SAFE_INTEGER
client.query(
  q.Add(q.ToInteger(maxInt.toString()), 1)
)
.then((ret) => console.log(ret))
.catch((err) => console.error(
  'Error: [%s] %s: %s',
  err.name,
  err.message,
  err.errors()[0].description,
))
9007199254740992
Query metrics:
  •    bytesIn:  45

  •   bytesOut:  29

  • computeOps:   1

  •    readOps:   0

  •   writeOps:   0

  •  readBytes:   0

  • writeBytes:   0

  •  queryTime: 0ms

  •    retries:   0

Floating-point values

Some host languages, such as JavaScript, opportunistically "upgrade" floating-point values to integers when there is no decimal portion. For example, 2.0 gets upgraded to 2.

If your application depends on receiving floating-point values, then you may need to ensure that floating-point values maintain their decimal portions when included in a query. You can achieve that goal with the ToDouble function:

client.query(
  [ 2.0, q.ToDouble(2.0) ]
)
.then((ret) => console.log(ret))
.catch((err) => console.error(
  'Error: [%s] %s: %s',
  err.name,
  err.message,
  err.errors()[0].description,
))
[ 2, 2.0 ]
Query metrics:
  •    bytesIn:  19

  •   bytesOut:  20

  • computeOps:   1

  •    readOps:   0

  •   writeOps:   0

  •  readBytes:   0

  • writeBytes:   0

  •  queryTime: 0ms

  •    retries:   0

Dates

Fauna’s Date type may be equivalent to your host language’s date type, or it may not. The best way include a date in a query is to use the ToDate function on a date cast as a string:

var date = new Date(Date.UTC(2022, 3, 13, 12, 0, 0))
var dateString = date.toLocaleDateString({
  calendar: 'iso8601',
  year: 'numeric',
  month: 'numeric',
  day: 'numeric',
})
client.query(
  [ dateString, q.ToDate(dateString) ]
)
.then((ret) => console.log(ret))
.catch((err) => console.error(
  'Error: [%s] %s: %s',
  err.name,
  err.message,
  err.errors()[0].description,
))
[ '2022-04-13', Date("2022-04-13") ]
Query metrics:
  •    bytesIn:  39

  •   bytesOut:  50

  • computeOps:   1

  •    readOps:   0

  •   writeOps:   0

  •  readBytes:   0

  • writeBytes:   0

  •  queryTime: 0ms

  •    retries:   0

References

Fauna References are compound values that include a collection reference and a document ID. As a compound value, a Reference is a unique identifier for a document in a particular database.

While it is common practice in an SQL-based relational database to simply use row ids as the primary key, we recommend that you avoid extracting just the document ID. By using just a document ID instead of a complete Reference, you need to compose references for queries, and extract document IDs from responses. And a document ID, by itself, does not identity a distinct document.

References for "schema" documents, those that define AccessProvider, Collections, Databases, User-defined functions, and Indexes, use a name string as their primary identifier and are expressed using their "reference" function and name. For example:

client.query([
  q.AccessProvider('Auth0-myapp'),
  q.Collection('spells'),
  q.Database('prydain'),
  q.Function('increment'),
  q.Index('spells_by_element'),
])
.then((ret) => console.log(ret))
.catch((err) => console.error(
  'Error: [%s] %s: %s',
  err.name,
  err.message,
  err.errors()[0].description,
))
[
  AccessProvider("Auth0-myapp"),
  Collection("spells"),
  Database("prydain"),
  Function("increment"),
  Index("spells_by_element")
]
Query metrics:
  •    bytesIn: 137

  •   bytesOut: 371

  • computeOps:   1

  •    readOps:   0

  •   writeOps:   0

  •  readBytes: 129

  • writeBytes:   0

  •  queryTime: 1ms

  •    retries:   0

Sets

Fauna sets are groups of documents. An Indexes is the primary manifestation of a set, but single-item sets can be created with the Singleton function, and the following functions can be used to combine or manipulate sets:

All

Tests whether all of the provided values are true.

Any

Tests whether any of the provided values are true.

Count

Counts the items in an array or set.

Difference

Returns the set of items in one set that are missing from additional sets.

Distinct

Returns the set of distinct items within a set.

Events

Returns the set of events describing the history of a set or document.

Filter

Fetches specific items from a set.

Intersection

Returns the set of items that exist in all sets.

IsEmpty

Tests whether an array or set is empty.

IsNonEmpty

Tests whether an array or set contains items.

Join

Combines the items in a set with set’s indexed values.

Match

Returns the set of items that match search terms.

Max

Returns the largest value in a list of numbers.

Mean

Returns the average value of the items in an array or set.

Min

Returns the smallest value in a list of numbers.

Range

Returns a subset of a set, in the specified range.

Reduce

Reduce an array or set to a result via a lambda function.

Reverse

Reverses the order of the items in a set.

Singleton

Returns a single-item set containing the provided document reference.

Sum

Sums the items in an array or set.

Union

Returns a set that combines the items in multiple sets.

There is no host-language representation of a set that can be expressed in FQL.

Timestamps

Fauna’s Timestamp type may be equivalent to your host language’s timestamp type, or it may not. Fauna Timestamps have nanosecond resolution, which can require more precision than your host language’s integer types.

The best way to include a numeric timestamp in a query is to express it as a string, and convert it to a Fauna integer with ToInteger, and then into a timestamp with Epoch:

var timestamp = new Date(Date.UTC(2022, 3, 13, 12, 0, 0, 123))
var timestampString = timestamp.valueOf().toString() + '000000'
client.query(
  [ timestampString, q.Epoch(q.ToInteger(timestampString), 'nanoseconds') ]
)
.then((ret) => console.log(ret))
.catch((err) => console.error(
  'Error: [%s] %s: %s',
  err.name,
  err.message,
  err.errors()[0].description,
))
[ '1649851200123000000', Time("2022-04-13T12:00:00.123Z") ]
Query metrics:
  •    bytesIn:  91

  •   bytesOut:  71

  • computeOps:   1

  •    readOps:   0

  •   writeOps:   0

  •  readBytes:   0

  • writeBytes:   0

  •  queryTime: 0ms

  •    retries:   0

If you have an ISO 8601-formatted timestamp, you can use the ToTime function:

var timestamp = new Date(Date.UTC(2022, 3, 13, 12, 0, 0, 123))
var timestampString = timestamp.toISOString()
client.query(
  [ timestampString, q.ToTime(timestampString) ]
)
.then((ret) => console.log(ret))
.catch((err) => console.error(
  'Error: [%s] %s: %s',
  err.name,
  err.message,
  err.errors()[0].description,
))
[ '2022-04-13T12:00:00.123Z', Time("2022-04-13T12:00:00.123Z") ]
Query metrics:
  •    bytesIn:  67

  •   bytesOut:  76

  • computeOps:   1

  •    readOps:   0

  •   writeOps:   0

  •  readBytes:   0

  • writeBytes:   0

  •  queryTime: 0ms

  •    retries:   0

Extract values from responses

Fauna responses are expressed as JSON, but include Fauna types where necessary.

var date = new Date(Date.UTC(2022, 3, 13, 12, 0, 0))
var dateString = date.toLocaleDateString({
  calendar: 'iso8601',
  year: 'numeric',
  month: 'numeric',
  day: 'numeric',
})
var timestamp = new Date(Date.UTC(2022, 3, 13, 12, 0, 0, 123))
var timestampString = timestamp.toISOString()

;(async () => {
  var res = await client.query({
    boolean: true,
    null: null,
    number: Math.max(12, 7, 48, 3),
    decimal: 2.1,
    large_int: q.Add(Number.MAX_SAFE_INTEGER, 1),
    bytes: new Uint8Array([0x1, 0x2, 0x3]),
    string: ['FQL', 'is', 'powerful'].join(' '),
    array: 'FQL is functional!'.split(' '),
    object: { first: 'Alan', last: 'Turing' },
    date: q.ToDate(dateString),
    timestamp: q.ToTime(timestampString),
    reference: q.Ref(q.Collection('Letters'), '101'),
  })
  .then((ret) => ret)
  .catch((err) => console.error(
    'Error: [%s] %s: %s',
    err.name,
    err.message,
    err.errors()[0].description,
  ))

  // handle response
  var extracted = Object.assign({}, res)
  console.log('From the response:', extracted)

  console.log(`\ndate:      ${new Date(res.date.date).toDateString()}`)
  console.log(`timestamp: ${new Date(res.timestamp.date).getTime()}`)

  console.log(`\nreference:               ${res.reference}`)
  console.log(`reference.collection:    ${res.reference.collection}`)
  console.log(`reference.collection.id: ${res.reference.collection.id}`)
  console.log(`reference.id:            ${res.reference.id}`)
})()
From the response:
{
  boolean: true,
  null: null,
  number: 48,
  decimal: 2.1,
  large_int: 9007199254740992,
  bytes: Bytes("AQID"),
  string: 'FQL is powerful',
  array: [ 'FQL', 'is', 'functional!' ],
  object: { first: 'Alan', last: 'Turing' },
  date: Date("2022-04-13"),
  timestamp: Time("2022-04-13T12:00:00.123Z"),
  reference: Ref(Collection("Letters"), "101")
}

date:      Tue Apr 12 2022
timestamp: 1649851200123

reference:               Ref(Collection("Letters"), "101")
reference.collection:    Collection("Letters")
reference.collection.id: Letters
reference.id:            101
Query metrics:
  •    bytesIn: 386

  •   bytesOut: 419

  • computeOps:   1

  •    readOps:   0

  •   writeOps:   0

  •  readBytes:   0

  • writeBytes:   0

  •  queryTime: 1ms

  •    retries:   0

The example demonstrates a technique for converting Dates, Timestamps, and References into host language values.

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!