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.

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.
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: []
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)
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.

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))
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]
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'}
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.

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].
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: []
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)
  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.

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))))
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]]]
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'}]}
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!