FQL v4 will be decommissioned on June 30, 2025. Ensure that you complete your migration from FQL v4 to FQL v10 by that date.

For more details, review the migration guide. Contact support@fauna.com with any questions.

List of terms using a UDF

Problem

You want to search an index using a list of terms but the number of terms is dynamic.

Solution

This is a more advanced version of the List of terms solution. This solution uses a user-defined function (UDF) to compose the query for every provided term.

client.query(
  q.CreateFunction({
    name: 'search_spells',
    body: q.Query(
      q.Lambda(
        'terms',
        q.Let(
          {
            term_array: q.Reduce(
              q.Lambda(
                ['acc', 'val'],
                q.Append(
                  [ q.Match(q.Index('spells_by_element'), q.Var('val')) ],
                  q.Var('acc')
                )
              ),
              [],
              q.Var('terms')
            ),
            search_set: q.If(
              q.GT(q.Count(q.Var('term_array')), 0),
              q.Union(q.Var('term_array')),
              q.Documents(q.Collection('spells'))
            ),
          },
          q.Map(
            q.Paginate(q.Var('search_set')),
            q.Lambda('ref', q.Get(q.Var('ref')))
          )
        )
      )
    ),
  })
)
.then((ret) => console.log(ret))
.catch((err) => console.error(
  'Error: [%s] %s: %s',
  err.name,
  err.message,
  err.errors()[0].description,
))
result = client.query(
  q.create_function({
    "name": "search_spells",
    "body": q.query(
      q.lambda_(
        "terms",
        q.let(
          {
            "term_array": q.reduce(
              q.lambda_(
                ["acc", "val"],
                q.append(
                  [ q.match(q.index("spells_by_element"), q.var("val")) ],
                  q.var("acc")
                )
              ),
              [],
              q.var("terms")
            ),
            "search_set": q.if_(
              q.gt(q.count(q.var("term_array")), 0),
              q.union(q.var("term_array")),
              q.documents(q.collection("spells"))
            )
          },
          q.map_(
            q.lambda_("ref", q.get(q.var("ref"))),
            q.paginate(q.var("search_set"))
          )
        )
      )
    )
  })
)
print(result)
res, err := client.Query(
	f.CreateFunction(
		f.Obj{
			"name": "search_spells",
			"body": f.Query(
				f.Lambda(
					"terms",
					f.Let().Bind(
						"term_array", f.Reduce(
							f.Lambda(
								f.Arr{"acc", "val"},
								f.Append(
									f.Arr{
										f.MatchTerm(
											f.Index("spells_by_element"),
											f.Var("val"),
										),
									},
									f.Var("acc"),
								),
							),
							f.Arr{},
							f.Var("terms"),
						),
					).Bind(
						"search_set", f.If(
							f.GT(f.Count(f.Var("term_array")), 0),
							f.Union(f.Var("term_array")),
							f.Documents(f.Collection("spells")),
						),
					).In(
						f.Map(
							f.Paginate(f.Var("search_set")),
							f.Lambda("ref", f.Get(f.Var("ref"))),
						),
					),
				),
			),
		},
	))

if err != nil {
	fmt.Fprintln(os.Stderr, err)
} else {
	fmt.Println(res)
}
try
{
    Value result = await client.Query(
        CreateFunction(
            Obj(
                "name", "search_spells",
                "body", Query(
                    Lambda(
                        "terms",
                        Let(
                            "term_array",
                            Reduce(
                                Lambda(
                                    Arr("acc", "val"),
                                    Append(
                                        Arr(
                                            Match(
                                                Index("spells_by_element"),
                                                Var("val")
                                            )
                                        ),
                                        Var("acc")
                                    )
                                ),
                                Arr(),
                                Var("terms")
                            ),
                            "search_set",
                            If(
                                GT(Count(Var("term_array")), 0),
                                Union(Var("term_array")),
                                Documents(Collection("spells"))
                            )
                        ).In(
                            Map(
                                Paginate(Var("search_set")),
                                Lambda("ref", Get(Var("ref")))
                            )
                        )
                    )
                )
            )
        )
    );
    Console.WriteLine(result);
}
catch (Exception e)
{
    Console.WriteLine($"ERROR: {e.Message}");
}
CreateFunction({
  name: "search_spells",
  body: Query(
    Lambda(
      "terms",
      Let(
        {
          term_array: Reduce(
            Lambda(
              ["acc", "val"],
              Append(
                [ Match(Index("spells_by_element"), Var("val")) ],
                Var("acc")
              )
            ),
            [],
            Var("terms")
          ),
          search_set: If(
            GT(Count(Var("term_array")), 0),
            Union(Var("term_array")),
            Documents(Collection("spells"))
          )
        },
        Map(Paginate(Var("search_set")), Lambda("ref", Get(Var("ref"))))
      )
    )
  )
})
Query metrics:
  •    bytesIn:  563

  •   bytesOut:  661

  • computeOps:    1

  •    readOps:    0

  •   writeOps:    1

  •  readBytes:   26

  • writeBytes:  704

  •  queryTime: 33ms

  •    retries:    0

You can now call the UDF with an array of terms to search:

client.query(
  q.Call(q.Function('search_spells'), ['earth'])
)
.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("spells"), "181388642088911360"),
      ts: 1629831125560000,
      data: { name: "Hippo's Wallow", element: [ 'water', 'earth' ] }
    }
  ]
}
result = client.query(
  q.call(q.function("search_spells"), ["earth"])
)
print(result)
{'data': [{'ref': Ref(id=181388642088911360, collection=Ref(id=spells, collection=Ref(id=collections))), 'ts': 1629831361330000, 'data': {'name': "Hippo's Wallow", 'element': ['water', 'earth']}}]}
res, err := client.Query(
	f.Call(f.Function("search_spells"), f.Arr{"earth"}))

if err != nil {
	fmt.Fprintln(os.Stderr, err)
} else {
	fmt.Println(res)
}
map[data:[map[data:map[element:[water earth] name:Hippo's Wallow] ref:{181388642088911360 0xc0000925a0 0xc0000925a0 <nil>} ts:1629835747470000]]]
try
{
    Value result = await client.Query(
        Call(Function("search_spells"), Arr("earth"))
    );
    Console.WriteLine(result);
}
catch (Exception e)
{
    Console.WriteLine($"ERROR: {e.Message}");
}
ObjectV(data: Arr(ObjectV(ref: RefV(id = "181388642088911360", collection = RefV(id = "spells", collection = RefV(id = "collections"))),ts: LongV(1629836365330000),data: ObjectV(name: StringV(Hippo's Wallow),element: Arr(StringV(water), StringV(earth))))))
Call(Function("search_spells"), ["earth"])
{
  data: [
    {
      ref: Ref(Collection("spells"), "181388642088911360"),
      ts: 1629503268260000,
      data: { name: "Hippo's Wallow", element: [ 'water', 'earth' ] }
    }
  ]
}
Query metrics:
  •    bytesIn:   59

  •   bytesOut:  232

  • computeOps:    1

  •    readOps:    2

  •   writeOps:    0

  •  readBytes:  162

  • writeBytes:    0

  •  queryTime: 13ms

  •    retries:    0

or with a different array:

client.query(
  q.Call(q.Function('search_spells'), ['earth', 'fire'])
)
.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("spells"), "181388642046968320"),
      ts: 1629831125560000,
      data: {
        name: 'Fire Beak',
        element: [ 'air', 'fire' ],
        spellbook: Ref(Collection("spellbooks"), "181388642139243008")
      }
    },
    {
      ref: Ref(Collection("spells"), "181388642071085568"),
      ts: 1629831125560000,
      data: {
        name: "Water Dragon's Claw",
        element: [ 'water', 'fire' ],
        spellbook: Ref(Collection("spellbooks"), "181388642139243008")
      }
    },
    {
      ref: Ref(Collection("spells"), "181388642088911360"),
      ts: 1629831125560000,
      data: { name: "Hippo's Wallow", element: [ 'water', 'earth' ] }
    }
  ]
}
result = client.query(
  q.call(q.function("search_spells"), ["earth", "fire"])
)
print(result)
{'data': [{'ref': Ref(id=181388642046968320, collection=Ref(id=spells, collection=Ref(id=collections))), 'ts': 1629831361330000, 'data': {'name': 'Fire Beak', 'element': ['air', 'fire'], 'spellbook': Ref(id=181388642139243008, collection=Ref(id=spellbooks, collection=Ref(id=collections)))}}, {'ref': Ref(id=181388642071085568, collection=Ref(id=spells, collection=Ref(id=collections))), 'ts': 1629831361330000, 'data': {'name': "Water Dragon's Claw", 'element': ['water', 'fire'], 'spellbook': Ref(id=181388642139243008, collection=Ref(id=spellbooks, collection=Ref(id=collections)))}}, {'ref': Ref(id=181388642088911360, collection=Ref(id=spells, collection=Ref(id=collections))), 'ts': 1629831361330000, 'data': {'name': "Hippo's Wallow", 'element': ['water', 'earth']}}]}
res, err := client.Query(
	f.Call(f.Function("search_spells"), f.Arr{"earth", "fire"}))

if err != nil {
	fmt.Fprintln(os.Stderr, err)
} else {
	fmt.Println(res)
}
map[data:[map[data:map[element:[air fire] name:Fire Beak spellbook:{181388642139243008 0xc000109b00 0xc000109b00 <nil>}] ref:{181388642046968320 0xc000109920 0xc000109920 <nil>} ts:1629835747470000] map[data:map[element:[water fire] name:Water Dragon's Claw spellbook:{181388642139243008 0xc000109ec0 0xc000109ec0 <nil>}] ref:{181388642071085568 0xc000109ce0 0xc000109ce0 <nil>} ts:1629835747470000] map[data:map[element:[water earth] name:Hippo's Wallow] ref:{181388642088911360 0xc0001700c0 0xc0001700c0 <nil>} ts:1629835747470000]]]
try
{
    Value result = await client.Query(
        Call(Function("search_spells"), Arr("earth", "fire"))
    );
    Console.WriteLine(result);
}
catch (Exception e)
{
    Console.WriteLine($"ERROR: {e.Message}");
}
ObjectV(data: Arr(ObjectV(ref: RefV(id = "181388642046968320", collection = RefV(id = "spells", collection = RefV(id = "collections"))),ts: LongV(1629836365330000),data: ObjectV(name: StringV(Fire Beak),element: Arr(StringV(air), StringV(fire)),spellbook: RefV(id = "181388642139243008", collection = RefV(id = "spellbooks", collection = RefV(id = "collections"))))), ObjectV(ref: RefV(id = "181388642071085568", collection = RefV(id = "spells", collection = RefV(id = "collections"))),ts: LongV(1629836365330000),data: ObjectV(name: StringV(Water Dragon's Claw),element: Arr(StringV(water), StringV(fire)),spellbook: RefV(id = "181388642139243008", collection = RefV(id = "spellbooks", collection = RefV(id = "collections"))))), ObjectV(ref: RefV(id = "181388642088911360", collection = RefV(id = "spells", collection = RefV(id = "collections"))),ts: LongV(1629836365330000),data: ObjectV(name: StringV(Hippo's Wallow),element: Arr(StringV(water), StringV(earth))))))
Call(Function("search_spells"), ["earth", "fire"])
{
  data: [
    {
      ref: Ref(Collection("spells"), "181388642046968320"),
      ts: 1629830703650000,
      data: {
        name: 'Fire Beak',
        element: [ 'air', 'fire' ],
        spellbook: Ref(Collection("spellbooks"), "181388642139243008")
      }
    },
    {
      ref: Ref(Collection("spells"), "181388642071085568"),
      ts: 1629830703650000,
      data: {
        name: "Water Dragon's Claw",
        element: [ 'water', 'fire' ],
        spellbook: Ref(Collection("spellbooks"), "181388642139243008")
      }
    },
    {
      ref: Ref(Collection("spells"), "181388642088911360"),
      ts: 1629830703650000,
      data: { name: "Hippo's Wallow", element: [ 'water', 'earth' ] }
    }
  ]
}
Query metrics:
  •    bytesIn:   66

  •   bytesOut:  914

  • computeOps:    1

  •    readOps:    5

  •   writeOps:    0

  •  readBytes:  503

  • writeBytes:    0

  •  queryTime: 13ms

  •    retries:    0

Discussion

You pass an array of terms in the Call function and the UDF uses Reduce to compose a Match expression for each term. When there are terms, a Union of the results is returned, otherwise the list of documents in the spells collection is returned.

Union combines all of the results for every term, which is equivalent to an "or" query. If you replace Union with Intersection, only the results common to every term are returned, which is equivalent to an "and" query.

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!