Announcing New Functions in FQL

We’re pleased to announce new FQL capabilities that provide better support for query patterns common to smaller scale deployments, and favor flexibility and expressivity over raw scalability. With these updates, users are able to write more concise and powerful FQL statements with Range(), Filter(), Reduce(), and Merge() functions.



Please note that these new functions are in Preview mode, which means that they will be continuing to improve over the next month and we invite you to send us feedback about their use.

New functionality has been added via a combination of new FQL functions and enhancements to existing ones. To learn more, please check out our documentation.



Range() function Preview

Range(set, from, to)

Range() provides the ability to limit a set based on lower and upper bounds of its natural order. For example, given the index:

{ "name": "users_by_name", "source": Collection("users"), "terms": [], "values": [{"field": ["data", "name"]}, {"field": "ref"}] }

Range() may be used to get a range of users by name:

Range(Match("users_by_name"), "Brown", "Smith")

If a set’s tuples are longer than the bounds passed to Range(), the limit applies based on the prefix the bound covers. For example, if the above index covered discrete fields for last name and first name, an array argument to Range() will be interpreted as a tuple prefix:

Range(Match("users_by_last_first"), ["Brown", "A"], "Smith")

Range() is inclusive. Other range-like predicates to be added are RangeLT, RangeLTE, RangeGT, and RangeGTE, which let you bound the set only on one side, or which can be combined to specify upper and lower bound exclusivity.



Filter() function

Filter(set/array/page, predicate)

Before this release, Filter() took an array or page and filtered its elements based on a predicate function. It has been enhanced to work on sets, in order to enable more ergonomic pagination and the ability to compose it with other set modifiers.

For example, let’s use Filter() on our index above to find all names over a certain length:

Filter(Match("users_by_name"), (name, r) => GT(Length(name), 20))

Reduce() function Preview

Reduce(set/array/page, init, fn)

We’ve added a Reduce() function to FQL. You may be familiar with “fold” or “reduce” from functional languages, which let you reduce a container such as an array, down to a summary value. FQL’s new Reduce() function brings this capability to sets, arrays, and pages in your FaunaDB queries.

Here’s how to count the number of users with an “all_users” index:

Reduce(Match("all_users"), 0, (x, count) => Add(1, count))

Format() function Preview

Format(template, v1, …, vn)

The new Format() function takes a string template and one or more values which are inserted into the template based on c-style formatting rules:

Format("Alex is %d years old.", 12)

See the documentation on Format() for more details, including template options.

Merge() function Preview

Merge(obj1, obj2, lambda)

Merge() combines two objects, and takes an optional lambda which lets you resolve conflicts between the two objects.

By default, if there is a conflict, the value from the second object is used:

Merge({ "a": 1, "b": 1 }, { "b": 2, "c": 2 }) { "a": 1, "b": 2, "c": 2 }

The optional lambda is used to control how conflicts are resolved:

Merge({ "a": 1, "b": 1 }, { "b": 2, "c": 2 }, (k, l, r) => Add(l, r)) { "a": 1, "b": 3, "c": 2 }

Translating OpenCRUD predicates

Listening to our users

The request for many of these new functions came from our friends at GraphCMS, who helped identify additional areas for growth in our FQL API. Creating these new functions has enabled us translate OpenCRUD predicates into queries on a single field sorted index, which was another request from GraphCMS that we were excited to fulfill.

Given the below index, which has no partition terms, and covers a field and the document ref:



{ "name": "coll_by_x", "source": Collection("a_collection"), "terms": [], "values": [{"field": ["data", "x"]}, {"field": "ref"}] }

Below are FQL representations of OpenCRUD single field equality predicates:



field (equals):

Range(Match("coll_by_x"), <value>)

field_not (not equals):

Filter(Match("coll_by_x"), (x, r) => Not(Equals(x, <value>)))

field_in (in list):

Union(Map(<values>, n => Range(Match("coll_by_x"), n))

field_not_in (not in list):

Filter(Match("coll_by_x"), (x, r) => And(Map(<values>, v => Not(Equals(v, x)))))

Conclusion

Range(), Filter(), Reduce(), Format(), and Merge() allow you to write more expressive FQL, and push more computational logic down to the database. Please visit our documentation to learn more. And please let us know what you think so that we can incorporate your feedback into the formal release.

What other functions would you like to see implemented in FaunaDB? Please reach out to me on our Community Slack and describe any other features that would make FaunaDB an obvious choice for your next project.