User-defined functions

User-defined functions (UDFs) allow developers to combine built-in or user-defined Fauna Query Language functions into named queries that you can execute repeatedly.

By default, UDFs run with the privileges of the current query session. For example, if your client code connects to Fauna using a key with the server role, any called UDFs run with server privileges by default. When you assign a specific role to a UDF, the UDF executes with the assigned role’s privileges which can differ from the caller’s.

This section demonstrates a few of the ways that UDFs can be helpful.

First, create some documents in a collection called "inventory":

try
{
    Value result = await client.Query(
        Map(
            Arr(
                Arr(1, "avocados", 100, 3.99),
                Arr(2, "limes", 30, 0.35),
                Arr(3, "cilantro", 100, 1.49)
            ),
            Lambda(
                Arr("id", "name", "quantity", "price"),
                Create(
                    Ref(Collection("inventory"), Var("id")),
                    Obj(
                        "data", Obj(
                            "name", Var("name"),
                            "quantity", Var("quantity"),
                            "price", Var("price")
                        )
                    )
                )
            )
        )
    );
    Console.WriteLine(result);
}
catch (Exception e)
{
    Console.WriteLine($"ERROR: {e.Message}");
}
Arr(ObjectV(ref: RefV(id = "1", collection = RefV(id = "inventory", collection = RefV(id = "collections"))),ts: LongV(1660238452240000),data: ObjectV(name: StringV(avocados),quantity: LongV(100),price: DoubleV(3.99))), ObjectV(ref: RefV(id = "2", collection = RefV(id = "inventory", collection = RefV(id = "collections"))),ts: LongV(1660238452240000),data: ObjectV(name: StringV(limes),quantity: LongV(30),price: DoubleV(0.35))), ObjectV(ref: RefV(id = "3", collection = RefV(id = "inventory", collection = RefV(id = "collections"))),ts: LongV(1660238452240000),data: ObjectV(name: StringV(cilantro),quantity: LongV(100),price: DoubleV(1.49))))
result, err := client.Query(
	f.Map(
		f.Arr{
			f.Arr{1, "avocados", 100, 3.99},
			f.Arr{2, "limes", 30, 0.35},
			f.Arr{3, "cilantro", 100, 1.49},
		},
		f.Lambda(
			f.Arr{"id", "name", "quantity", "price"},
			f.Create(
				f.Ref(f.Collection("inventory"), f.Var("id")),
				f.Obj{
					"data": f.Obj{
						"name": f.Var("name"),
						"quantity": f.Var("quantity"),
						"price": f.Var("price"),
					},
				},
			),
		),
	))

if err != nil {
	fmt.Fprintln(os.Stderr, err)
} else {
	fmt.Println(result)
}
[map[data:map[name:avocados price:3.99 quantity:100] ref:{1 0x14000096270 0x14000096270 <nil>} ts:1660238472990000] map[data:map[name:limes price:0.35 quantity:30] ref:{2 0x14000096480 0x14000096480 <nil>} ts:1660238472990000] map[data:map[name:cilantro price:1.49 quantity:100] ref:{3 0x14000096690 0x14000096690 <nil>} ts:1660238472990000]]
await client.query(
  q.Map(
    [
      [1, 'avocados', 100, 3.99],
      [2, 'limes', 30, 0.35],
      [3, 'cilantro', 100, 1.49],
    ],
    q.Lambda(
      ['id', 'name', 'quantity', 'price'],
      q.Create(
        q.Ref(q.Collection('inventory'), q.Var('id')),
        {
          data: {
            name: q.Var('name'),
            quantity: q.Var('quantity'),
            price: q.Var('price'),
          },
        }
      )
    )
  )
)
.then((res) => {
  console.log(res)
  return res
})
.catch((err) => console.error(
  'Error: [%s] %s: %s\n%s',
  err.name,
  err.message,
  err.errors()[0].description,
  err.errors()[0].cause[0],
))
[
  {
    ref: Ref(Collection("inventory"), "1"),
    ts: 1660238549140000,
    data: { name: 'avocados', quantity: 100, price: 3.99 }
  },
  {
    ref: Ref(Collection("inventory"), "2"),
    ts: 1660238549140000,
    data: { name: 'limes', quantity: 30, price: 0.35 }
  },
  {
    ref: Ref(Collection("inventory"), "3"),
    ts: 1660238549140000,
    data: { name: 'cilantro', quantity: 100, price: 1.49 }
  }
]
result = client.query(
  q.map_(
    q.lambda_(
      ["id", "name", "quantity", "price"],
      q.create(
        q.ref(q.collection("inventory"), q.var("id")),
        {
          "data": {
            "name": q.var("name"),
            "quantity": q.var("quantity"),
            "price": q.var("price"),
          }
        }
      )
    ),
    [
      [1, 'avocados', 100, 3.99],
      [2, 'limes', 30, 0.35],
      [3, 'cilantro', 100, 1.49],
    ]
  )
)
print(result)
[{'ref': Ref(id=1, collection=Ref(id=inventory, collection=Ref(id=collections))), 'ts': 1660238551170000, 'data': {'name': 'avocados', 'quantity': 100, 'price': 3.99}}, {'ref': Ref(id=2, collection=Ref(id=inventory, collection=Ref(id=collections))), 'ts': 1660238551170000, 'data': {'name': 'limes', 'quantity': 30, 'price': 0.35}}, {'ref': Ref(id=3, collection=Ref(id=inventory, collection=Ref(id=collections))), 'ts': 1660238551170000, 'data': {'name': 'cilantro', 'quantity': 100, 'price': 1.49}}]
CreateCollection({ name: "inventory" })
Map(
  [
    [1, "avocados", 100, 3.99],
    [2, "limes", 30, 0.35],
    [3, "cilantro", 100, 1.49],
  ],
  Lambda(
    ["id", "name", "quantity", "price"],
    Create(
      Ref(Collection("inventory"), Var("id")),
      {
        data: {
          name: Var("name"),
          quantity: Var("quantity"),
          price: Var("price"),
        },
      }
    )
  )
)
[
  {
    ref: Collection("inventory"),
    ts: 1660238557230000,
    history_days: 30,
    name: 'inventory'
  },
  {
    ref: Ref(Collection("inventory"), "1"),
    ts: 1660238557250000,
    data: { name: 'avocados', quantity: 100, price: 3.99 }
  },
  {
    ref: Ref(Collection("inventory"), "2"),
    ts: 1660238557250000,
    data: { name: 'limes', quantity: 30, price: 0.35 }
  },
  {
    ref: Ref(Collection("inventory"), "3"),
    ts: 1660238557250000,
    data: { name: 'cilantro', quantity: 100, price: 1.49 }
  }
]
Query metrics:
  •    bytesIn:  323

  •   bytesOut:  577

  • computeOps:    1

  •    readOps:    0

  •   writeOps:    3

  •  readBytes:   42

  • writeBytes:  672

  •  queryTime: 18ms

  •    retries:    0

Create an index to look up inventory items by name:

try
{
    Value result = await client.Query(
        CreateIndex(
            Obj(
                "name", "known_names",
                "source", Collection("inventory"),
                "terms", Arr(
                    Obj("field", Arr("data", "name"))
                )
            )
        )
    );
    Console.WriteLine(result);
}
catch (Exception e)
{
    Console.WriteLine($"ERROR: {e.Message}");
}
ObjectV(ref: RefV(id = "known_names", collection = RefV(id = "indexes")),ts: LongV(1660238455680000),active: BooleanV(True),serialized: BooleanV(True),name: StringV(known_names),source: RefV(id = "inventory", collection = RefV(id = "collections")),terms: Arr(ObjectV(field: Arr(StringV(data), StringV(name)))),partitions: LongV(1))
result, err := client.Query(
	f.CreateIndex(
		f.Obj{
			"name": "known_names",
			"source": f.Collection("inventory"),
			"terms": f.Arr{
				f.Obj{"field": f.Arr{"data", "name"}},
			},
		},
	))

if err != nil {
	fmt.Fprintln(os.Stderr, err)
} else {
	fmt.Println(result)
}
map[active:true name:known_names partitions:1 ref:{known_names 0x1400009c240 0x1400009c240 <nil>} serialized:true source:{inventory 0x1400009c330 0x1400009c330 <nil>} terms:[map[field:[data name]]] ts:1660238474050000]
client.query(
  q.CreateIndex({
    name: 'known_names',
    source: q.Collection('inventory'),
    terms: [
      { field: ['data', 'name'] },
    ],
  })
)
.then((ret) => console.log(ret))
.catch((err) => console.error(
  'Error: [%s] %s: %s\n%s',
  err.name,
  err.message,
  err.errors()[0].description,
  err.errors()[0].cause[0],
))
{
  ref: Index("known_names"),
  ts: 1660238549240000,
  active: true,
  serialized: true,
  name: 'known_names',
  source: Collection("inventory"),
  terms: [ { field: [ 'data', 'name' ] } ],
  partitions: 1
}
result = client.query(
  q.create_index({
    "name": "known_names",
    "source": q.collection("inventory"),
    "terms": [
      { "field": ["data", "name"] }
    ]
  })
)
print(result)
{'ref': Ref(id=known_names, collection=Ref(id=indexes)), 'ts': 1660238552200000, 'active': True, 'serialized': True, 'name': 'known_names', 'source': Ref(id=inventory, collection=Ref(id=collections)), 'terms': [{'field': ['data', 'name']}], 'partitions': 1}
CreateIndex({
  name: "known_names",
  source: Collection("inventory"),
  terms: [
    { field: ["data", "name"] },
  ],
})
{
  ref: Index("known_names"),
  ts: 1660238557540000,
  active: true,
  serialized: true,
  name: 'known_names',
  source: Collection("inventory"),
  terms: [ { field: [ 'data', 'name' ] } ],
  partitions: 1
}
Query metrics:
  •    bytesIn:   133

  •   bytesOut:   295

  • computeOps:     1

  •    readOps:     0

  •   writeOps:     4

  •  readBytes: 1,621

  • writeBytes:   810

  •  queryTime:  14ms

  •    retries:     0

Before you add a new item to this collection, you might want to perform data validation to ensure consistent document structure. For this demonstration, assume that the name must exist within the collection, and the price must be a Double (a double-precision, floating-point number). With a UDF, you can create a function that performs the validation and creates the new document only if its fields contain valid values.

try
{
    Value result = await client.Query(
        CreateFunction(
            Obj(
                "name", "create_new_inventory", (1)
                "body", Query(
                    Lambda( (2)
                        Arr("name", "quantity", "price"),
                        Let( (3)
                            "is_name_known", If( (4)
                                GT(
                                    Count(
                                        Match(
                                            Index("known_names"),
                                            Var("name")
                                        )
                                    )
                                ),
                                true,
                                false
                            ),
                            "is_price_double", IsDouble(Var("price"))
                        ).In(
                            If(
                                And(
                                    Var("is_name_known"),
                                    Var("is_price_double")
                                ),
                                Create( (5)
                                    Collection("inventory"),
                                    Obj(
                                        "data", Obj(
                                            "name", Var("name"),
                                            "quantity", Var("quantity"),
                                            "price", Var("price")
                                        )
                                    )
                                ),
                                Abort( (6)
                                    Concat(
                                        Arr(
                                            If(
                                                Var("is_name_known"),
                                                "",
                                                "Name is unknown. "
                                            ),
                                            If(
                                                Var("is_price_double"),
                                                "",
                                                "Price is not a double. "
                                            )
                                        ),
                                        ""
                                    )
                                )
                            )
                        )
                    )
                )
            )
        )
    );
    Console.WriteLine(result);
}
catch (Exception e)
{
    Console.WriteLine($"ERROR: {e.Message}");
}
ObjectV(ref: RefV(id = "create_new_inventory", collection = RefV(id = "functions")),ts: LongV(1660238459570000),name: StringV(create_new_inventory),body: QueryV(System.Collections.Generic.Dictionary`2[System.String,FaunaDB.Query.Expr]))
result, err := client.Query(
	f.CreateFunction(
		f.Obj{
			"name": "create_new_inventory", (1)
			"body": f.Query(
				f.Lambda( (2)
					f.Arr{"name", "quantity", "price"},
					f.Let().Bind( (3)
						"is_name_known", f.If( (4)
							f.GT(
								f.Count(
									f.MatchTerm(
										f.Index("known_names"),
										f.Var("name"),
									),
								),
								0,
							),
							true,
							false,
						),
					).Bind(
						"is_price_double", f.IsDouble(f.Var("price")),
					).In(
						f.If(
							f.And(
								f.Var("is_name_known"),
								f.Var("is_price_double"),
							),
							f.Create( (5)
								f.Collection("inventory"),
								f.Obj{
									"data": f.Obj{
										"name": f.Var("name"),
										"quantity": f.Var("quantity"),
										"price": f.Var("price"),
									},
								},
							),
							f.Abort( (6)
								f.Concat(
									f.Arr{
										f.If(
											f.Var("is_name_known"),
											"",
											"Name is unknown. ",
										),
										f.If(
											f.Var("is_price_double"),
											"",
											"Price is not a double. ",
										),
									},
									f.Separator(""),
								),
							),
						),
					),
				),
			),
		},
	))

if err != nil {
	fmt.Fprintln(os.Stderr, err)
} else {
	fmt.Println(result)
}
map[body:{[123 34 97 112 105 95 118 101 114 115 105 111 110 34 58 34 52 34 44 34 108 97 109 98 100 97 34 58 91 34 110 97 109 101 34 44 34 113 117 97 110 116 105 116 121 34 44 34 112 114 105 99 101 34 93 44 34 101 120 112 114 34 58 123 34 108 101 116 34 58 123 34 105 115 95 110 97 109 101 95 107 110 111 119 110 34 58 123 34 105 102 34 58 123 34 103 116 34 58 91 123 34 99 111 117 110 116 34 58 123 34 109 97 116 99 104 34 58 123 34 105 110 100 101 120 34 58 34 107 110 111 119 110 95 110 97 109 101 115 34 125 44 34 116 101 114 109 115 34 58 123 34 118 97 114 34 58 34 110 97 109 101 34 125 125 125 44 48 93 125 44 34 116 104 101 110 34 58 116 114 117 101 44 34 101 108 115 101 34 58 102 97 108 115 101 125 44 34 105 115 95 112 114 105 99 101 95 100 111 117 98 108 101 34 58 123 34 105 115 95 100 111 117 98 108 101 34 58 123 34 118 97 114 34 58 34 112 114 105 99 101 34 125 125 125 44 34 105 110 34 58 123 34 105 102 34 58 123 34 97 110 100 34 58 91 123 34 118 97 114 34 58 34 105 115 95 110 97 109 101 95 107 110 111 119 110 34 125 44 123 34 118 97 114 34 58 34 105 115 95 112 114 105 99 101 95 100 111 117 98 108 101 34 125 93 125 44 34 116 104 101 110 34 58 123 34 99 114 101 97 116 101 34 58 123 34 99 111 108 108 101 99 116 105 111 110 34 58 34 105 110 118 101 110 116 111 114 121 34 125 44 34 112 97 114 97 109 115 34 58 123 34 111 98 106 101 99 116 34 58 123 34 100 97 116 97 34 58 123 34 111 98 106 101 99 116 34 58 123 34 110 97 109 101 34 58 123 34 118 97 114 34 58 34 110 97 109 101 34 125 44 34 112 114 105 99 101 34 58 123 34 118 97 114 34 58 34 112 114 105 99 101 34 125 44 34 113 117 97 110 116 105 116 121 34 58 123 34 118 97 114 34 58 34 113 117 97 110 116 105 116 121 34 125 125 125 125 125 125 44 34 101 108 115 101 34 58 123 34 97 98 111 114 116 34 58 123 34 99 111 110 99 97 116 34 58 91 123 34 105 102 34 58 123 34 118 97 114 34 58 34 105 115 95 110 97 109 101 95 107 110 111 119 110 34 125 44 34 116 104 101 110 34 58 34 34 44 34 101 108 115 101 34 58 34 78 97 109 101 32 105 115 32 117 110 107 110 111 119 110 46 32 34 125 44 123 34 105 102 34 58 123 34 118 97 114 34 58 34 105 115 95 112 114 105 99 101 95 100 111 117 98 108 101 34 125 44 34 116 104 101 110 34 58 34 34 44 34 101 108 115 101 34 58 34 80 114 105 99 101 32 105 115 32 110 111 116 32 97 32 100 111 117 98 108 101 46 32 34 125 93 44 34 115 101 112 97 114 97 116 111 114 34 58 34 34 125 125 125 125 125]} name:create_new_inventory ref:{create_new_inventory 0x140002044b0 0x140002044b0 <nil>} ts:1660238475050000]
client.query(
  q.CreateFunction({
    name: 'create_new_inventory', (1)
    body:
      q.Query(
        q.Lambda( (2)
          ['name', 'quantity', 'price'],
          q.Let( (3)
            {
              is_name_known: q.If( (4)
                q.GT(
                  q.Count(
                    q.Match(q.Index('known_names'), q.Var('name'))
                  ),
                  0
                ),
                true,
                false
              ),
              is_price_double: q.IsDouble(q.Var('price')),
            },
            q.If(
              q.And(q.Var('is_name_known'), q.Var('is_price_double')),
              q.Create( (5)
                q.Collection('inventory'),
                {
                  data: {
                    name: q.Var('name'),
                    quantity: q.Var('quantity'),
                    price: q.Var('price'),
                  },
                },
              ),
              q.Abort( (6)
                q.Concat(
                  [
                    q.If(
                      q.Var('is_name_known'),
                      '',
                      'Name is unknown. '
                    ),
                    q.If(
                      q.Var('is_price_double'),
                      '',
                      'Price is not a double. ',
                    ),
                  ],
                  ''
                )
              )
            )
          )
        )
      ),
  })
)
.then((ret) => console.log(ret))
.catch((err) => console.error(
  'Error: [%s] %s: %s\n%s',
  err.name,
  err.message,
  err.errors()[0].description,
  err.errors()[0].cause[0],
))
{
  ref: Function("create_new_inventory"),
  ts: 1660238549340000,
  name: 'create_new_inventory',
  body: Query(Lambda(["name", "quantity", "price"], Let({"is_name_known": If(GT(Count(Match(Index("known_names"), Var("name"))), 0), true, false), "is_price_double": IsDouble(Var("price"))}, If(And(Var("is_name_known"), Var("is_price_double")), Create(Collection("inventory"), {"data": {"name": Var("name"), "quantity": Var("quantity"), "price": Var("price")}}), Abort(Concat([If(Var("is_name_known"), "", "Name is unknown. "), If(Var("is_price_double"), "", "Price is not a double. ")], ""))))))
}
result = client.query(
  q.create_function({
    "name": "create_new_inventory", (1)
    "body": q.query(
      q.lambda_( (2)
        ["name", "quantity", "price"],
        q.let( (3)
          {
            "is_name_known": q.if_( (4)
              q.gt(
                q.count(
                  q.match(q.index("known_names"), q.var("name"))
                ),
                0
              ),
              True,
              False
            ),
            "is_price_double": q.is_double(q.var("price"))
          },
          q.if_(
            q.and_(q.var("is_name_known"), q.var("is_price_double")),
            q.create( (5)
              q.collection("inventory"),
              {
                "data": {
                  "name": q.var("name"),
                  "quantity": q.var("quantity"),
                  "price": q.var("price")
                }
              }
            ),
            q.abort( (6)
              q.concat([
                q.if_(
                  q.var("is_name_known"),
                  "",
                  "Name is unknown. "
                ),
                q.if_(
                  q.var("is_price_double"),
                  "",
                  "Price is not a double. "
                ),
              ])
            )
          )
        )
      )
    )
  })
)
print(result)
{'ref': Ref(id=create_new_inventory, collection=Ref(id=functions)), 'ts': 1660238553230000, 'name': 'create_new_inventory', 'body': Query({'api_version': '4', 'lambda': ['name', 'quantity', 'price'], 'expr': {'let': {'is_name_known': {'if': {'gt': [{'count': {'match': {'index': 'known_names'}, 'terms': {'var': 'name'}}}, 0]}, 'then': True, 'else': False}, 'is_price_double': {'is_double': {'var': 'price'}}}, 'in': {'if': {'and': [{'var': 'is_name_known'}, {'var': 'is_price_double'}]}, 'then': {'create': {'collection': 'inventory'}, 'params': {'object': {'data': {'object': {'name': {'var': 'name'}, 'quantity': {'var': 'quantity'}, 'price': {'var': 'price'}}}}}}, 'else': {'abort': {'concat': [{'if': {'var': 'is_name_known'}, 'then': '', 'else': 'Name is unknown. '}, {'if': {'var': 'is_price_double'}, 'then': '', 'else': 'Price is not a double. '}]}}}}})}
CreateFunction({
  name: "create_new_inventory", (1)
  body:
    Query(
      Lambda( (2)
        // the function takes three parameters
        ["name", "quantity", "price"],
        Let( (3)
          {
            // define two variables
            is_name_known: If( (4)
              GT(Count(Match(Index("known_names"), Var("name"))), 0),
              true,
              false
            ),
            is_price_double: IsDouble(Var("price")),
          },
          If(
            And(Var("is_name_known"), Var("is_price_double")),
            Create( (5)
              Collection("inventory"),
              {
                data: {
                  name: Var("name"),
                  quantity: Var("quantity"),
                  price: Var("price")
                },
              }
            ),
            Abort( (6)
              Concat(
                [
                  If(Var("is_name_known"), "", "Name is unknown. "),
                  If(Var("is_price_double"), "", "Price is not a double. "),
                ],
                ""
              )
            )
          )
        )
      )
    ),
})
{
  ref: Function("create_new_inventory"),
  ts: 1660238557830000,
  name: 'create_new_inventory',
  body: Query(Lambda(["name", "quantity", "price"], Let({"is_name_known": If(GT(Count(Match(Index("known_names"), Var("name"))), 0), true, false), "is_price_double": IsDouble(Var("price"))}, If(And(Var("is_name_known"), Var("is_price_double")), Create(Collection("inventory"), {"data": {"name": Var("name"), "quantity": Var("quantity"), "price": Var("price")}}), Abort(Concat([If(Var("is_name_known"), "", "Name is unknown. "), If(Var("is_price_double"), "", "Price is not a double. ")], ""))))))
}
Query metrics:
  •    bytesIn:  737

  •   bytesOut:  842

  • computeOps:    1

  •    readOps:    0

  •   writeOps:    1

  •  readBytes:   33

  • writeBytes:  849

  •  queryTime: 16ms

  •    retries:    0

1 Give the function a name: create_new_inventory.
2 Implement the function logic in a Lambda or anonymous function. This function takes three inputs: name, quantity, and price.
3 To test for the two conditions, use Let to create two variables that both return a boolean:
  • The is_name_known variable checks the known_names index for existence of the brand input.

  • The is_price_double variable checks if the price input is a Double.

4 The If function behaves like a ternary operator: "If X then Y, otherwise Z." If the number of results matching name in the known_names index is greater than 0, the function returns true. Otherwise, it returns false.
5 Notice that the If function encloses the Create function. When the checks pass, Create is called. Otherwise, Abort is called to terminate the transaction with a custom error message.
6 The error message depends on the two boolean variables, is_name_known and is_price_double.

To run the UDF, use the Call function:

try
{
    Value result = await client.Query(
        Call(
            Function("create_new_inventory"),
            Arr("avocados", 100, 2.89)
        )
    );
    Console.WriteLine(result);
}
catch (Exception e)
{
    Console.WriteLine($"ERROR: {e.Message}");
}
ObjectV(ref: RefV(id = "339717246987272704", collection = RefV(id = "inventory", collection = RefV(id = "collections"))),ts: LongV(1660238463300000),data: ObjectV(name: StringV(avocados),quantity: LongV(100),price: DoubleV(2.89)))
result, err := client.Query(
	f.Call(
		f.Function("create_new_inventory"),
		f.Arr{"avocados", 100, 2.89},
	))

if err != nil {
	fmt.Fprintln(os.Stderr, err)
} else {
	fmt.Println(result)
}
map[data:map[name:avocados price:2.89 quantity:100] ref:{339717260365005312 0x140001159e0 0x140001159e0 <nil>} ts:1660238476050000]
client.query(
  q.Call(q.Function('create_new_inventory'), ['avocados', 100, 2.89])
)
.then((ret) => console.log(ret))
.catch((err) => console.error(
  'Error: [%s] %s: %s',
  err.name,
  err.message,
  err.errors()[0].description,
))
{
  ref: Ref(Collection("inventory"), "339717337306366464"),
  ts: 1660238549430000,
  data: { name: 'avocados', quantity: 100, price: 2.89 }
}
result = client.query(
  q.call(q.function("create_new_inventory"), ["avocados", 100, 2.89])
)
print(result)
{'ref': Ref(id=339717342383571456, collection=Ref(id=inventory, collection=Ref(id=collections))), 'ts': 1660238554270000, 'data': {'name': 'avocados', 'quantity': 100, 'price': 2.89}}
Call(Function("create_new_inventory"), ["avocados", 100, 2.89])
{
  ref: Ref(Collection("inventory"), "339717346418491904"),
  ts: 1660238558120000,
  data: { name: 'avocados', quantity: 100, price: 2.89 }
}
Query metrics:
  •    bytesIn:   78

  •   bytesOut:  218

  • computeOps:    1

  •    readOps:    1

  •   writeOps:    1

  •  readBytes:   67

  • writeBytes:  367

  •  queryTime: 15ms

  •    retries:    0

The example runs successfully because the string avocados is present in the known_names index, and 2.89 is a double. However, if you try to run the function with faulty inputs, you get an error:

try
{
    Value result = await client.Query(
        Call(
            Function("create_new_inventory"),
            Arr("bananas", 80, "1.89")
        )
    );
    Console.WriteLine(result);
}
catch (Exception e)
{
    Console.WriteLine($"ERROR: {e.Message}");
}
ERROR: call error: Calling the function resulted in an error.
result, err := client.Query(
	f.Call(
		f.Function("create_new_inventory"),
		f.Arr{"bananas", 100, "1.89"},
	))

if err != nil {
	fmt.Fprintln(os.Stderr, err)
} else {
	fmt.Println(result)
}
Response error 400. Errors: [](call error): Calling the function resulted in an error., details: [{[expr in else] transaction aborted Name is unknown. Price is not a double. }]
client.query(
  q.Call(q.Function('create_new_inventory'), ['bananas', 80, '1.89'])
)
.then((ret) => console.log(ret))
.catch((err) => console.error(
  'Error: [%s] %s: %s\n%s',
  err.name,
  err.message,
  err.errors()[0].description,
  err.errors()[0].cause[0],
))
Error: ['BadRequest'] 'call error': 'Calling the function resulted in an error.'
{
  position: [ 'expr', 'in', 'else' ],
  code: 'transaction aborted',
  description: 'Name is unknown. Price is not a double. '
}
try:
  result = client.query(
    q.call(q.function("create_new_inventory"), ["bananas", 80, "1.89"])
  )
  print(result)
except:
  print("Error: ", sys.exc_info()[0], sys.exc_info()[1])
Error:  <class 'faunadb.errors.BadRequest'> ErrorData(code='call error', description='Calling the function resulted in an error.', position=[], failures=None)
Call(Function("create_new_inventory"), ["bananas", 80, "1.89"])
{
  errors: [
    {
      position: [],
      code: 'call error',
      description: 'Calling the function resulted in an error.',
      cause: [
        {
          position: [
            'expr',
            'in',
            'else'
          ],
          code: 'transaction aborted',
          description: 'Name is unknown. Price is not a double. '
        }
      ]
    }
  ]
}
Query metrics:
  •    bytesIn:  78

  •   bytesOut: 237

  • computeOps:   1

  •    readOps:   0

  •   writeOps:   0

  •  readBytes:   0

  • writeBytes:   0

  •  queryTime: 5ms

  •    retries:   0

In the future, if you need to refine the create_new_item function, you can update it and continue to use the client code unmodified, provided the function accepts the same inputs. For example, if you want to allow the function to run with the server role regardless of the caller’s role, update the role field of the UDF:

try
{
    Value result = await client.Query(
        Update(
            Function("create_new_inventory"),
            Obj("role", "server")
        )
    );
    Console.WriteLine(result);
}
catch (Exception e)
{
    Console.WriteLine($"ERROR: {e.Message}");
}
ObjectV(ref: RefV(id = "create_new_inventory", collection = RefV(id = "functions")),ts: LongV(1660238470430000),name: StringV(create_new_inventory),body: QueryV(System.Collections.Generic.Dictionary`2[System.String,FaunaDB.Query.Expr]),role: StringV(server))
result, err := client.Query(
	f.Update(
		f.Function("create_new_inventory"),
		f.Obj{"role": "server"},
	))

if err != nil {
	fmt.Fprintln(os.Stderr, err)
} else {
	fmt.Println(result)
}
map[body:{[123 34 97 112 105 95 118 101 114 115 105 111 110 34 58 34 52 34 44 34 108 97 109 98 100 97 34 58 91 34 110 97 109 101 34 44 34 113 117 97 110 116 105 116 121 34 44 34 112 114 105 99 101 34 93 44 34 101 120 112 114 34 58 123 34 108 101 116 34 58 123 34 105 115 95 110 97 109 101 95 107 110 111 119 110 34 58 123 34 105 102 34 58 123 34 103 116 34 58 91 123 34 99 111 117 110 116 34 58 123 34 109 97 116 99 104 34 58 123 34 105 110 100 101 120 34 58 34 107 110 111 119 110 95 110 97 109 101 115 34 125 44 34 116 101 114 109 115 34 58 123 34 118 97 114 34 58 34 110 97 109 101 34 125 125 125 44 48 93 125 44 34 116 104 101 110 34 58 116 114 117 101 44 34 101 108 115 101 34 58 102 97 108 115 101 125 44 34 105 115 95 112 114 105 99 101 95 100 111 117 98 108 101 34 58 123 34 105 115 95 100 111 117 98 108 101 34 58 123 34 118 97 114 34 58 34 112 114 105 99 101 34 125 125 125 44 34 105 110 34 58 123 34 105 102 34 58 123 34 97 110 100 34 58 91 123 34 118 97 114 34 58 34 105 115 95 110 97 109 101 95 107 110 111 119 110 34 125 44 123 34 118 97 114 34 58 34 105 115 95 112 114 105 99 101 95 100 111 117 98 108 101 34 125 93 125 44 34 116 104 101 110 34 58 123 34 99 114 101 97 116 101 34 58 123 34 99 111 108 108 101 99 116 105 111 110 34 58 34 105 110 118 101 110 116 111 114 121 34 125 44 34 112 97 114 97 109 115 34 58 123 34 111 98 106 101 99 116 34 58 123 34 100 97 116 97 34 58 123 34 111 98 106 101 99 116 34 58 123 34 110 97 109 101 34 58 123 34 118 97 114 34 58 34 110 97 109 101 34 125 44 34 112 114 105 99 101 34 58 123 34 118 97 114 34 58 34 112 114 105 99 101 34 125 44 34 113 117 97 110 116 105 116 121 34 58 123 34 118 97 114 34 58 34 113 117 97 110 116 105 116 121 34 125 125 125 125 125 125 44 34 101 108 115 101 34 58 123 34 97 98 111 114 116 34 58 123 34 99 111 110 99 97 116 34 58 91 123 34 105 102 34 58 123 34 118 97 114 34 58 34 105 115 95 110 97 109 101 95 107 110 111 119 110 34 125 44 34 116 104 101 110 34 58 34 34 44 34 101 108 115 101 34 58 34 78 97 109 101 32 105 115 32 117 110 107 110 111 119 110 46 32 34 125 44 123 34 105 102 34 58 123 34 118 97 114 34 58 34 105 115 95 112 114 105 99 101 95 100 111 117 98 108 101 34 125 44 34 116 104 101 110 34 58 34 34 44 34 101 108 115 101 34 58 34 80 114 105 99 101 32 105 115 32 110 111 116 32 97 32 100 111 117 98 108 101 46 32 34 125 93 44 34 115 101 112 97 114 97 116 111 114 34 58 34 34 125 125 125 125 125]} name:create_new_inventory ref:{create_new_inventory 0x1400007f950 0x1400007f950 <nil>} role:server ts:1660238477900000]
client.query(
  q.Update(
    q.Function('create_new_inventory'),
    { role: 'server' }
  )
)
.then((ret) => console.log(ret))
.catch((err) => console.error(
  'Error: [%s] %s: %s\n%s',
  err.name,
  err.message,
  err.errors()[0].description,
  err.errors()[0].cause[0],
))
{
  ref: Function("create_new_inventory"),
  ts: 1660238549600000,
  name: 'create_new_inventory',
  body: Query(Lambda(["name", "quantity", "price"], Let({"is_name_known": If(GT(Count(Match(Index("known_names"), Var("name"))), 0), true, false), "is_price_double": IsDouble(Var("price"))}, If(And(Var("is_name_known"), Var("is_price_double")), Create(Collection("inventory"), {"data": {"name": Var("name"), "quantity": Var("quantity"), "price": Var("price")}}), Abort(Concat([If(Var("is_name_known"), "", "Name is unknown. "), If(Var("is_price_double"), "", "Price is not a double. ")], "")))))),
  role: 'server'
}
result = client.query(
  q.update(
    q.function("create_new_inventory"),
    { "role": "server" }
  )
)
print(result)
{'ref': Ref(id=create_new_inventory, collection=Ref(id=functions)), 'ts': 1660238556380000, 'name': 'create_new_inventory', 'body': Query({'api_version': '4', 'lambda': ['name', 'quantity', 'price'], 'expr': {'let': {'is_name_known': {'if': {'gt': [{'count': {'match': {'index': 'known_names'}, 'terms': {'var': 'name'}}}, 0]}, 'then': True, 'else': False}, 'is_price_double': {'is_double': {'var': 'price'}}}, 'in': {'if': {'and': [{'var': 'is_name_known'}, {'var': 'is_price_double'}]}, 'then': {'create': {'collection': 'inventory'}, 'params': {'object': {'data': {'object': {'name': {'var': 'name'}, 'quantity': {'var': 'quantity'}, 'price': {'var': 'price'}}}}}}, 'else': {'abort': {'concat': [{'if': {'var': 'is_name_known'}, 'then': '', 'else': 'Name is unknown. '}, {'if': {'var': 'is_price_double'}, 'then': '', 'else': 'Price is not a double. '}]}}}}}), 'role': 'server'}
Update(Function("create_new_inventory"), { role: "server" })
{
  ref: Function("create_new_inventory"),
  ts: 1660238558690000,
  name: 'create_new_inventory',
  body: Query(Lambda(["name", "quantity", "price"], Let({"is_name_known": If(GT(Count(Match(Index("known_names"), Var("name"))), 0), true, false), "is_price_double": IsDouble(Var("price"))}, If(And(Var("is_name_known"), Var("is_price_double")), Create(Collection("inventory"), {"data": {"name": Var("name"), "quantity": Var("quantity"), "price": Var("price")}}), Abort(Concat([If(Var("is_name_known"), "", "Name is unknown. "), If(Var("is_price_double"), "", "Price is not a double. ")], "")))))),
  role: 'server'
}
Query metrics:
  •    bytesIn:   84

  •   bytesOut:  858

  • computeOps:    1

  •    readOps:    0

  •   writeOps:    1

  •  readBytes:  563

  • writeBytes:  593

  •  queryTime: 12ms

  •    retries:    0

Limitations

  • Fauna imposes a 30-second transaction timeout, terminating any transactions that exceed the limit. Transaction termination can happen with UDFs that take too long to execute.

  • Fauna terminates transactions when the execution of a UDF exceeds available memory.

  • Recursion is possible but is limited to a depth of 200 calls.

  • In some contexts, such as within index bindings, using "server read-only" keys, or attribute-based access control, Fauna may restrict UDFs from performing write or read operations.

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!