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.

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}");
}
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)
}
System.out.println(
    client.query(
        CreateFunction(
            Obj(
                "name", Value("search_spells"),
                "body", Query(
                    Lambda(
                        "terms",
                        Let(
                            "term_array", Reduce(
                                Lambda(
                                    Arr(Value("acc"), Value("val")),
                                    Append(
                                        Arr(
                                            Match(
                                                Index("spells_by_element"),
                                                Var("val")
                                            )
                                        ),
                                        Var("acc")
                                    )
                                ),
                                Arr(),
                                Var("terms")
                            ),
                            "search_set", If(
                                GT(Count(Var("term_array")), Value(0)),
                                Union(Var("term_array")),
                                Documents(Collection("spells"))
                            )
                        ).in(
                            Map(
                                Paginate(Var("search_set")),
                                Lambda("ref", Get(Var("ref")))
                            )
                        )
                    )
                )
            )
        )
    ).get());
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)
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:

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))))))
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]]]
System.out.println(
    client.query(
        Call(Function("search_spells"), Arr(Value("earth")))
    ).get());
{data: [{ref: ref(id = "181388642088911360", collection = ref(id = "spells", collection = ref(id = "collections"))), ts: 1629836886190000, data: {name: "Hippo's Wallow", element: ["water", "earth"]}}]}
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']}}]}
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:

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))))))
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]]]
System.out.println(
    client.query(
        Call(
            Function("search_spells"),
            Arr(Value("earth"), Value("fire"))
        )
    ).get());
{data: [{ref: ref(id = "181388642046968320", collection = ref(id = "spells", collection = ref(id = "collections"))), ts: 1629836886190000, 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: 1629836886190000, 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: 1629836886190000, data: {name: "Hippo's Wallow", element: ["water", "earth"]}}]}
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']}}]}
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!