Defensive query strategies

This section describes recommended best practices for handling queries on non-existent documents or fields.

When a document does not exist

Fauna document relationships are commonly made by storing References to other documents. However, if document A stores a reference to document B, the reference to document B is not changed in document A when document B is deleted. Subsequent queries that process document A fail when they attempt to fetch document B.

You can handle references to deleted documents by calling the If and Exists functions to test for the existence of a document before attempting to fetch it.

For example, the following query fails because the related document does not exist.

client.query(
  q.Let(
    {
      spellbook: q.Get(q.Ref(q.Collection('spellbooks'), '101')),
      owner_ref: q.Select(['data', 'owner'], q.Var('spellbook')),
      owner: q.Get(q.Var('owner_ref')),
    },
    {
      spellbook: q.Select(['data', 'name'], q.Var('spellbook')),
      owner: q.Select(['data', 'name'], q.Var('owner')),
    }
  )
)
.then((ret) => console.log(ret))
.catch((err) => console.error(
  'Error: [%s] %s: %s',
  err.name,
  err.message,
  err.errors()[0].description,
))
Error: [NotFound] instance not found: Document not found.
try:
  result = client.query(
    q.let(
      {
        "spellbook": q.get(q.ref(q.collection("spellbooks"), "101")),
        "owner_ref": q.select(["data", "owner"], q.var("spellbook")),
        "owner":     q.get(q.var("owner_ref")),
      },
      {
        "spellbook": q.select(["data", "name"], q.var("spellbook")),
        "owner":     q.select(["data", "name"], q.var("owner"))
      }
    )
  )
  print(result)
except:
  print("Error: ", sys.exc_info()[0], sys.exc_info()[1])
Error:  <class 'faunadb.errors.NotFound'> ErrorData(code='instance not found', description='Document not found.', position=['let', 2, 'owner'], failures=None)
result, err := client.Query(
	f.Let().Bind(
		"spellbook", f.Get(f.Ref(f.Collection("spellbooks"), "101")),
	).Bind(
		"owner_ref", f.Select(f.Arr{"data", "owner"}, f.Var("spellbook")),
	).Bind(
		"owner",     f.Get(f.Var("owner_ref")),
	).In(
		f.Obj{
			"spellbook": f.Select(f.Arr{"data", "name"}, f.Var("spellbook")),
			"owner":     f.Select(f.Arr{"data", "name"}, f.Var("owner")),
		},
	))

if err != nil {
	fmt.Fprintln(os.Stderr, err)
} else {
	fmt.Println(result)
}
Response error 404. Errors: [let//owner](instance not found): Document not found., details: []
try
{
    Value result = await client.Query(
        Let(
            "spellbook", Get(Ref(Collection("spellbooks"), "101")),
            "owner_ref", Select(Arr("data", "owner"), Var("spellbook")),
            "owner",     Get(Var("owner_ref"))
        ).In(
            Obj(
                "spellbook", Select(Arr("data", "name"), Var("spellbook")),
                "owner",     Select(Arr("data", "name"), Var("owner"))
            )
        )
    );

    Console.WriteLine(result);
}
catch (Exception e)
{
    Console.WriteLine($"ERROR: {e.Message}");
}
ERROR: instance not found: Document not found.
Let(
  {
    spellbook: Get(Ref(Collection('spellbooks'), '101')),
    owner_ref: Select(['data', 'owner'], Var('spellbook')),
    owner: Get(Var('owner_ref')),
  },
  {
    spellbook: Select(['data', 'name'], Var('spellbook')),
    owner: Select(['data', 'name'], Var('owner')),
  }
)
{
  errors: [
    {
      position: [
        'let',
        2,
        'owner'
      ],
      code: 'instance not found',
      description: 'Document not found.'
    }
  ]
}
Query metrics:
  •    bytesIn: 327

  •   bytesOut: 107

  • computeOps:   1

  •    readOps:   0

  •   writeOps:   0

  •  readBytes:   0

  • writeBytes:   0

  •  queryTime: 5ms

  •    retries:   0

The following query succeeds because it checks for the existence of the related document. The non-existence of the document can be reported, for example, by substituting an object that includes the DISAPPEARED name.

client.query(
  q.Let(
    {
      spellbook: q.Get(q.Ref(q.Collection('spellbooks'), '101')),
      owner_ref: q.Select(['data', 'owner'], q.Var('spellbook')),
      owner: q.If(
        q.Exists(q.Var('owner_ref')),
        q.Get(q.Var('owner_ref')),
        { data: { name: 'DISAPPEARED' } },
      ),
    },
    {
      spellbook: q.Select(['data', 'name'], q.Var('spellbook')),
      owner: q.Select(['data', 'name'], q.Var('owner')),
    }
  )
)
.then((ret) => console.log(ret))
.catch((err) => console.error(
  'Error: [%s] %s: %s',
  err.name,
  err.message,
  err.errors()[0].description,
))
{ spellbook: "Dallben's Spellbook", owner: 'DISAPPEARED' }
try:
  result = client.query(
    q.let(
      {
        "spellbook": q.get(q.ref(q.collection("spellbooks"), "101")),
        "owner_ref": q.select(["data", "owner"], q.var("spellbook")),
        "owner":     q.if_(
          q.exists(q.var("owner_ref")),
          q.get(q.var("owner_ref")),
          { "data": { "name": "DISAPPEATED" }}
        )
      },
      {
        "spellbook": q.select(["data", "name"], q.var("spellbook")),
        "owner":     q.select(["data", "name"], q.var("owner"))
      }
    )
  )
  print(result)
except:
  print("Error: ", sys.exc_info()[0], sys.exc_info()[1])
{'spellbook': "Dallben's Spellbook", 'owner': 'DISAPPEATED'}
result, err := client.Query(
	f.Let().Bind(
		"spellbook", f.Get(f.Ref(f.Collection("spellbooks"), "101")),
	).Bind(
		"owner_ref", f.Select(f.Arr{"data", "owner"}, f.Var("spellbook")),
	).Bind(
		"owner",     f.If(
			f.Exists(f.Var("owner_ref")),
			f.Get(f.Var("owner_ref")),
			f.Obj{"data": f.Obj{"name": "DISAPPEARED"}},
		),
	).In(
		f.Obj{
			"spellbook": f.Select(f.Arr{"data", "name"}, f.Var("spellbook")),
			"owner":     f.Select(f.Arr{"data", "name"}, f.Var("owner")),
		},
	))

if err != nil {
	fmt.Fprintln(os.Stderr, err)
} else {
	fmt.Println(result)
}
map[owner:DISAPPEARED spellbook:Dallben's Spellbook]
try
{
    Value result = await client.Query(
        Let(
            "spellbook", Get(Ref(Collection("spellbooks"), "101")),
            "owner_ref", Select(Arr("data", "owner"), Var("spellbook")),
            "owner",     If(
                Exists(Var("owner_ref")),
                Get(Var("owner_ref")),
                Obj("data", Obj("name", "DISAPPEARED"))
            )
        ).In(
            Obj(
                "spellbook", Select(Arr("data", "name"), Var("spellbook")),
                "owner",     Select(Arr("data", "name"), Var("owner"))
            )
        )
    );

    Console.WriteLine(result);
}
catch (Exception e)
{
    Console.WriteLine($"ERROR: {e.Message}");
}
ObjectV(spellbook: StringV(Dallben's Spellbook),owner: StringV(DISAPPEARED))
Let(
  {
    spellbook: Get(Ref(Collection('spellbooks'), '101')),
    owner_ref: Select(['data', 'owner'], Var('spellbook')),
    owner: If(
      Exists(Var('owner_ref')),
      Get(Var('owner_ref')),
      { data: { name: 'DISAPPEARED' } },
    ),
  },
  {
    spellbook: Select(['data', 'name'], Var('spellbook')),
    owner: Select(['data', 'name'], Var('owner')),
  }
)
{ spellbook: "Dallben's Spellbook", owner: 'DISAPPEARED' }
Query metrics:
  •    bytesIn:  433

  •   bytesOut:   70

  • computeOps:    1

  •    readOps:    2

  •   writeOps:    0

  •  readBytes:  108

  • writeBytes:    0

  •  queryTime: 11ms

  •    retries:    0

When a field does not exist

The flexible Fauna data model allows documents with different structures to coexist in a single collection. When evaluating documents with inconsistent field structures, attempting to access a non-existent field causes your query to fail.

You can test for the existence of a field using the If and ContainsPath functions.

For example, the following query fails because not all documents contain the extra field.

client.query(
  q.Map(
    q.Paginate(q.Documents(q.Collection('Letters'))),
    q.Lambda(
      'ref',
      q.Let(
        { doc: q.Get(q.Var('ref')) },
        {
          letter: q.Select(['data', 'letter'], q.Var('doc')),
          extra: q.Concat(
            [
              'Extra',
              q.ToString(
                q.Select(['data', 'extra'], q.Var('doc'))
              ),
            ],
            '-'
          ),
        }
      )
    )
  )
)
.then((ret) => console.log(ret))
.catch((err) => console.error(
  'Error: [%s] %s: %s',
  err.name,
  err.message,
  err.errors()[0].description,
))
Error: [NotFound] value not found: Value not found at path [data,extra].
try:
  result = client.query(
    q.map_(
      q.lambda_(
        "ref",
        q.let(
          { "doc": q.get(q.var("ref")) },
          {
            "letter": q.select(["data", "letter"], q.var("doc")),
            "extra":  q.concat(
              [
                "Extra",
                q.to_string(
                  q.select(["data", "extra"], q.var("doc"))
                ),
              ],
              "-"
            )
          }
        )
      ),
      q.paginate(q.documents(q.collection("Letters"))),
    )
  )
  print(result)
except:
  print("Error: ", sys.exc_info()[0], sys.exc_info()[1])
Error:  <class 'faunadb.errors.NotFound'> ErrorData(code='value not found', description='Value not found at path [data,extra].', position=['map', 'expr', 'in', 'object', 'extra', 'concat', 1, 'to_string', 'from'], failures=None)
result, err := client.Query(
	f.Map(
		f.Paginate(f.Documents(f.Collection("Letters"))),
		f.Lambda(
			"ref",
			f.Let().Bind(
				"doc", f.Get(f.Var("ref")),
			).In(
				f.Obj{
					"letter": f.Select(f.Arr{"data", "letter"}, f.Var("doc")),
					"extra":  f.Concat(
						f.Arr{
							"Extra",
							f.ToString(
								f.Select(f.Arr{"data", "extra"}, f.Var("doc")),
							),
						},
						f.Separator("-"),
					),
				},
			),
		),
	))

if err != nil {
	fmt.Fprintln(os.Stderr, err)
} else {
	fmt.Println(result)
}
Response error 404. Errors: [map/expr/in/object/extra/concat//to_string/from](value not found): Value not found at path [data,extra]., details: []
try
{
    Value result = await client.Query(
        Map(
            Paginate(Documents(Collection("Letters"))),
            Lambda(
                "ref",
                Let(
                    "doc", Get(Var("ref"))
                ).In(
                    Obj(
                        "letter", Select(Arr("data", "letter"), Var("doc")),
                        "extra", Concat(
                            Arr(
                                "Extra",
                                ToStringExpr(
                                    Select(Arr("data", "extra"), Var("doc"))
                                )
                            ),
                            "-"
                        )
                    )
                )
            )
        )
    );

    Console.WriteLine(result);
}
catch (Exception e)
{
    Console.WriteLine($"ERROR: {e.Message}");
}
ERROR: value not found: Value not found at path [data,extra].
  Map(
    Paginate(Documents(Collection('Letters'))),
    Lambda(
      'ref',
      Let(
        { doc: Get(Var('ref')) },
        {
          letter: Select(['data', 'letter'], Var('doc')),
          extra: Concat(
            [
              'Extra',
              ToString(
                Select(['data', 'extra'], Var('doc'))
              ),
            ],
            '-'
          ),
        }
      )
    )
  )
{
  errors: [
    {
      position: [
        'map',
        'expr',
        'in',
        'object',
        'extra',
        'concat',
        1,
        'to_string',
        'from'
      ],
      code: 'value not found',
      description: 'Value not found at path [data,extra].'
    }
  ]
}
Query metrics:
  •    bytesIn:  321

  •   bytesOut:  171

  • computeOps:    1

  •    readOps:    0

  •   writeOps:    0

  •  readBytes:    0

  • writeBytes:    0

  •  queryTime: 29ms

  •    retries:    0

The following query succeeds because the existence of the extra field is checked and if the field does not exist, it can be reported, for example, by returning UNDEFINED.

client.query(
  q.Map(
    q.Paginate(q.Documents(q.Collection('Letters'))),
    q.Lambda(
      'ref',
      q.Let(
        { doc: q.Get(q.Var('ref')) },
        {
          letter: q.Select(['data', 'letter'], q.Var('doc')),
          extra: q.If(
            q.ContainsPath(['data', 'extra'], q.Var('doc')),
            q.Concat(
              [
                'Extra',
                q.ToString(
                  q.Select(['data', 'extra'], q.Var('doc'))
                ),
              ],
              '-'
            ),
            'UNDEFINED',
          ),
        }
      )
    )
  )
)
.then((ret) => console.log(ret))
.catch((err) => console.error(
  'Error: [%s] %s: %s',
  err.name,
  err.message,
  err.errors()[0].description,
))
{
  data: [
    { letter: 'A', extra: 'Extra-First' },
    { letter: 'B', extra: 'Extra-second' },
    { letter: 'C', extra: 'Extra-third' },
    { letter: 'D', extra: 'Extra-4th' },
    { letter: 'E', extra: 'Extra-fifth' },
    { letter: 'F', extra: 'Extra-sixth' },
    { letter: 'G', extra: 'Extra-seventh' },
    { letter: 'H', extra: 'Extra-eighth' },
    { letter: 'I', extra: 'Extra-9th' },
    { letter: 'J', extra: 'Extra-tenth' },
    { letter: 'K', extra: 'Extra-11' },
    { letter: 'L', extra: 'Extra-' },
    { letter: 'M', extra: 'UNDEFINED' },
    { letter: 'N', extra: 'Extra-14th' },
    { letter: 'O', extra: 'Extra-fifteenth' },
    { letter: 'P', extra: 'Extra-16th' },
    { letter: 'Q', extra: 'Extra-seventeenth' },
    { letter: 'R', extra: 'Extra-18th' },
    { letter: 'S', extra: 'Extra-19th' },
    { letter: 'T', extra: 'Extra-20th' },
    { letter: 'U', extra: 'Extra-21st' },
    { letter: 'V', extra: 'Extra-22nd' },
    { letter: 'W', extra: 'Extra-twenty-third' },
    { letter: 'X', extra: 'Extra-24' },
    { letter: 'Y', extra: 'Extra-24 + 1' },
    { letter: 'Z', extra: 'UNDEFINED' }
  ]
}
try:
  result = client.query(
    q.map_(
      q.lambda_(
        "ref",
        q.let(
          { "doc": q.get(q.var("ref")) },
          {
            "letter": q.select(["data", "letter"], q.var("doc")),
            "extra": q.if_(
              q.contains_path(["data", "extra"], q.var("doc")),
              q.concat(
                [
                  "Extra",
                  q.to_string(
                    q.select(["data", "extra"], q.var("doc"))
                  ),
                ],
                "-"
              ),
              "UNDEFINED"
            )
          }
        )
      ),
      q.paginate(q.documents(q.collection("Letters"))),
    )
  )
  print(result)
except:
  print("Error: ", sys.exc_info()[0], sys.exc_info()[1])
{'data': [{'letter': 'A', 'extra': 'Extra-First'}, {'letter': 'B', 'extra': 'Extra-second'}, {'letter': 'C', 'extra': 'Extra-third'}, {'letter': 'D', 'extra': 'Extra-4th'}, {'letter': 'E', 'extra': 'Extra-fifth'}, {'letter': 'F', 'extra': 'Extra-sixth'}, {'letter': 'G', 'extra': 'Extra-seventh'}, {'letter': 'H', 'extra': 'Extra-eighth'}, {'letter': 'I', 'extra': 'Extra-9th'}, {'letter': 'J', 'extra': 'Extra-tenth'}, {'letter': 'K', 'extra': 'Extra-11'}, {'letter': 'L', 'extra': 'Extra-'}, {'letter': 'M', 'extra': 'UNDEFINED'}, {'letter': 'N', 'extra': 'Extra-14th'}, {'letter': 'O', 'extra': 'Extra-fifteenth'}, {'letter': 'P', 'extra': 'Extra-16th'}, {'letter': 'Q', 'extra': 'Extra-seventeenth'}, {'letter': 'R', 'extra': 'Extra-18th'}, {'letter': 'S', 'extra': 'Extra-19th'}, {'letter': 'T', 'extra': 'Extra-20th'}, {'letter': 'U', 'extra': 'Extra-21st'}, {'letter': 'V', 'extra': 'Extra-22nd'}, {'letter': 'W', 'extra': 'Extra-twenty-third'}, {'letter': 'X', 'extra': 'Extra-24'}, {'letter': 'Y', 'extra': 'Extra-24 + 1'}, {'letter': 'Z', 'extra': 'UNDEFINED'}]}
result, err := client.Query(
	f.Map(
		f.Paginate(f.Documents(f.Collection("Letters"))),
		f.Lambda(
			"ref",
			f.Let().Bind(
				"doc", f.Get(f.Var("ref")),
			).In(
				f.Obj{
					"letter": f.Select(f.Arr{"data", "letter"}, f.Var("doc")),
					"extra": f.If(
						f.ContainsPath(f.Arr{"data", "extra"}, f.Var("doc")),
						f.Concat(
							f.Arr{
								"Extra",
								f.ToString(f.Select(f.Arr{"data", "extra"}, f.Var("doc"))),
							},
							f.Separator("-"),
						),
						"UNDEFINED",
					),
				},
			),
		),
	))

if err != nil {
	fmt.Fprintln(os.Stderr, err)
} else {
	fmt.Println(result)
}
map[data:[map[extra:Extra-First letter:A] map[extra:Extra-second letter:B] map[extra:Extra-third letter:C] map[extra:Extra-4th letter:D] map[extra:Extra-fifth letter:E] map[extra:Extra-sixth letter:F] map[extra:Extra-seventh letter:G] map[extra:Extra-eighth letter:H] map[extra:Extra-9th letter:I] map[extra:Extra-tenth letter:J] map[extra:Extra-11 letter:K] map[extra:Extra- letter:L] map[extra:UNDEFINED letter:M] map[extra:Extra-14th letter:N] map[extra:Extra-fifteenth letter:O] map[extra:Extra-16th letter:P] map[extra:Extra-seventeenth letter:Q] map[extra:Extra-18th letter:R] map[extra:Extra-19th letter:S] map[extra:Extra-20th letter:T] map[extra:Extra-21st letter:U] map[extra:Extra-22nd letter:V] map[extra:Extra-twenty-third letter:W] map[extra:Extra-24 letter:X] map[extra:Extra-24 + 1 letter:Y] map[extra:UNDEFINED letter:Z]]]
try
{
    Value result = await client.Query(
        Map(
            Paginate(Documents(Collection("Letters"))),
            Lambda(
                "ref",
                Let(
                    "doc", Get(Var("ref"))
                ).In(
                    Obj(
                        "letter", Select(Arr("data", "letter"), Var("doc")),
                        "extra", If(
                            ContainsPath(Arr("data", "extra"), Var("doc")),
                            Concat(
                                Arr(
                                    "Extra",
                                    ToStringExpr(Select(Arr("data", "extra"), Var("doc")))
                                ),
                                "-"
                            ),
                            "UNDEFINED"
                        )
                    )
                )
            )
        )
    );

    Console.WriteLine(result);
}
catch (Exception e)
{
    Console.WriteLine($"ERROR: {e.Message}");
}
ObjectV(data: Arr(ObjectV(letter: StringV(A),extra: StringV(Extra-First)), ObjectV(letter: StringV(B),extra: StringV(Extra-second)), ObjectV(letter: StringV(C),extra: StringV(Extra-third)), ObjectV(letter: StringV(D),extra: StringV(Extra-4th)), ObjectV(letter: StringV(E),extra: StringV(Extra-fifth)), ObjectV(letter: StringV(F),extra: StringV(Extra-sixth)), ObjectV(letter: StringV(G),extra: StringV(Extra-seventh)), ObjectV(letter: StringV(H),extra: StringV(Extra-eighth)), ObjectV(letter: StringV(I),extra: StringV(Extra-9th)), ObjectV(letter: StringV(J),extra: StringV(Extra-tenth)), ObjectV(letter: StringV(K),extra: StringV(Extra-11)), ObjectV(letter: StringV(L),extra: StringV(Extra-)), ObjectV(letter: StringV(M),extra: StringV(UNDEFINED)), ObjectV(letter: StringV(N),extra: StringV(Extra-14th)), ObjectV(letter: StringV(O),extra: StringV(Extra-fifteenth)), ObjectV(letter: StringV(P),extra: StringV(Extra-16th)), ObjectV(letter: StringV(Q),extra: StringV(Extra-seventeenth)), ObjectV(letter: StringV(R),extra: StringV(Extra-18th)), ObjectV(letter: StringV(S),extra: StringV(Extra-19th)), ObjectV(letter: StringV(T),extra: StringV(Extra-20th)), ObjectV(letter: StringV(U),extra: StringV(Extra-21st)), ObjectV(letter: StringV(V),extra: StringV(Extra-22nd)), ObjectV(letter: StringV(W),extra: StringV(Extra-twenty-third)), ObjectV(letter: StringV(X),extra: StringV(Extra-24)), ObjectV(letter: StringV(Y),extra: StringV(Extra-24 + 1)), ObjectV(letter: StringV(Z),extra: StringV(UNDEFINED))))
Map(
  Paginate(Documents(Collection('Letters'))),
  Lambda(
    'ref',
    Let(
      { doc: Get(Var('ref')) },
      {
        letter: Select(['data', 'letter'], Var('doc')),
        extra: If(
          ContainsPath(['data', 'extra'], Var('doc')),
          Concat(
            [
              'Extra',
              ToString(
                Select(['data', 'extra'], Var('doc'))
              ),
            ],
            '-'
          ),
          'UNDEFINED',
        ),
      }
    )
  )
)
{
  data: [
    { letter: 'A', extra: 'Extra-First' },
    { letter: 'B', extra: 'Extra-second' },
    { letter: 'C', extra: 'Extra-third' },
    { letter: 'D', extra: 'Extra-4th' },
    { letter: 'E', extra: 'Extra-fifth' },
    { letter: 'F', extra: 'Extra-sixth' },
    { letter: 'G', extra: 'Extra-seventh' },
    { letter: 'H', extra: 'Extra-eighth' },
    { letter: 'I', extra: 'Extra-9th' },
    { letter: 'J', extra: 'Extra-tenth' },
    { letter: 'K', extra: 'Extra-11' },
    { letter: 'L', extra: 'Extra-' },
    { letter: 'M', extra: 'UNDEFINED' },
    { letter: 'N', extra: 'Extra-14th' },
    { letter: 'O', extra: 'Extra-fifteenth' },
    { letter: 'P', extra: 'Extra-16th' },
    { letter: 'Q', extra: 'Extra-seventeenth' },
    { letter: 'R', extra: 'Extra-18th' },
    { letter: 'S', extra: 'Extra-19th' },
    { letter: 'T', extra: 'Extra-20th' },
    { letter: 'U', extra: 'Extra-21st' },
    { letter: 'V', extra: 'Extra-22nd' },
    { letter: 'W', extra: 'Extra-twenty-third' },
    { letter: 'X', extra: 'Extra-24' },
    { letter: 'Y', extra: 'Extra-24 + 1' },
    { letter: 'Z', extra: 'UNDEFINED' }
  ]
}
Query metrics:
  •    bytesIn:   408

  •   bytesOut:   981

  • computeOps:     3

  •    readOps:    34

  •   writeOps:     0

  •  readBytes: 3,119

  • writeBytes:     0

  •  queryTime:  37ms

  •    retries:     0

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!