diff options
Diffstat (limited to 'docs/ref')
| -rw-r--r-- | docs/ref/models/custom-lookups.txt | 407 | ||||
| -rw-r--r-- | docs/ref/models/index.txt | 2 | ||||
| -rw-r--r-- | docs/ref/models/lookups.txt | 207 | ||||
| -rw-r--r-- | docs/ref/models/querysets.txt | 2 |
4 files changed, 209 insertions, 409 deletions
diff --git a/docs/ref/models/custom-lookups.txt b/docs/ref/models/custom-lookups.txt deleted file mode 100644 index ef97a84e2a..0000000000 --- a/docs/ref/models/custom-lookups.txt +++ /dev/null @@ -1,407 +0,0 @@ -============== -Custom lookups -============== - -.. versionadded:: 1.7 - -.. module:: django.db.models.lookups - :synopsis: Custom lookups - -.. currentmodule:: django.db.models - -By default Django offers a wide variety of :ref:`built-in lookups -<field-lookups>` for filtering (for example, ``exact`` and ``icontains``). This -documentation explains how to write custom lookups and how to alter the working -of existing lookups. - -A simple Lookup example -~~~~~~~~~~~~~~~~~~~~~~~ - -Let's start with a simple custom lookup. We will write a custom lookup ``ne`` -which works opposite to ``exact``. ``Author.objects.filter(name__ne='Jack')`` -will translate to the SQL:: - - "author"."name" <> 'Jack' - -This SQL is backend independent, so we don't need to worry about different -databases. - -There are two steps to making this work. Firstly we need to implement the -lookup, then we need to tell Django about it. The implementation is quite -straightforward:: - - from django.db.models import Lookup - - class NotEqual(Lookup): - lookup_name = 'ne' - - def as_sql(self, qn, connection): - lhs, lhs_params = self.process_lhs(qn, connection) - rhs, rhs_params = self.process_rhs(qn, connection) - params = lhs_params + rhs_params - return '%s <> %s' % (lhs, rhs), params - -To register the ``NotEqual`` lookup we will just need to call -``register_lookup`` on the field class we want the lookup to be available. In -this case, the lookup makes sense on all ``Field`` subclasses, so we register -it with ``Field`` directly:: - - from django.db.models.fields import Field - Field.register_lookup(NotEqual) - -We can now use ``foo__ne`` for any field ``foo``. You will need to ensure that -this registration happens before you try to create any querysets using it. You -could place the implementation in a ``models.py`` file, or register the lookup -in the ``ready()`` method of an ``AppConfig``. - -Taking a closer look at the implementation, the first required attribute is -``lookup_name``. This allows the ORM to understand how to interpret ``name__ne`` -and use ``NotEqual`` to generate the SQL. By convention, these names are always -lowercase strings containing only letters, but the only hard requirement is -that it must not contain the string ``__``. - -We then need to define the ``as_sql`` method. This takes a ``SQLCompiler`` -object, called ``qn``, and the active database connection. ``SQLCompiler`` -objects are not documented, but the only thing we need to know about them is -that they have a ``compile()`` method which returns a tuple containing a SQL -string, and the parameters to be interpolated into that string. In most cases, -you don't need to use it directly and can pass it on to ``process_lhs()`` and -``process_rhs()``. - -A ``Lookup`` works against two values, ``lhs`` and ``rhs``, standing for -left-hand side and right-hand side. The left-hand side is usually a field -reference, but it can be anything implementing the :ref:`query expression API -<query-expression>`. The right-hand is the value given by the user. In the -example ``Author.objects.filter(name__ne='Jack')``, the left-hand side is a -reference to the ``name`` field of the ``Author`` model, and ``'Jack'`` is the -right-hand side. - -We call ``process_lhs`` and ``process_rhs`` to convert them into the values we -need for SQL using the ``qn`` object described before. These methods return -tuples containing some SQL and the parameters to be interpolated into that SQL, -just as we need to return from our ``as_sql`` method. In the above example, -``process_lhs`` returns ``('"author"."name"', [])`` and ``process_rhs`` returns -``('"%s"', ['Jack'])``. In this example there were no parameters for the left -hand side, but this would depend on the object we have, so we still need to -include them in the parameters we return. - -Finally we combine the parts into a SQL expression with ``<>``, and supply all -the parameters for the query. We then return a tuple containing the generated -SQL string and the parameters. - -A simple transformer example -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The custom lookup above is great, but in some cases you may want to be able to -chain lookups together. For example, let's suppose we are building an -application where we want to make use of the ``abs()`` operator. -We have an ``Experiment`` model which records a start value, end value and the -change (start - end). We would like to find all experiments where the change -was equal to a certain amount (``Experiment.objects.filter(change__abs=27)``), -or where it did not exceed a certain amount -(``Experiment.objects.filter(change__abs__lt=27)``). - -.. note:: - This example is somewhat contrived, but it demonstrates nicely the range of - functionality which is possible in a database backend independent manner, - and without duplicating functionality already in Django. - -We will start by writing a ``AbsoluteValue`` transformer. This will use the SQL -function ``ABS()`` to transform the value before comparison:: - - from django.db.models import Transform - - class AbsoluteValue(Transform): - lookup_name = 'abs' - - def as_sql(self, qn, connection): - lhs, params = qn.compile(self.lhs) - return "ABS(%s)" % lhs, params - -Next, lets register it for ``IntegerField``:: - - from django.db.models import IntegerField - IntegerField.register_lookup(AbsoluteValue) - -We can now run the queries we had before. -``Experiment.objects.filter(change__abs=27)`` will generate the following SQL:: - - SELECT ... WHERE ABS("experiments"."change") = 27 - -By using ``Transform`` instead of ``Lookup`` it means we are able to chain -further lookups afterwards. So -``Experiment.objects.filter(change__abs__lt=27)`` will generate the following -SQL:: - - SELECT ... WHERE ABS("experiments"."change") < 27 - -Subclasses of ``Transform`` usually only operate on the left-hand side of the -expression. Further lookups will work on the transformed value. Note that in -this case where there is no other lookup specified, Django interprets -``change__abs=27`` as ``change__abs__exact=27``. - -When looking for which lookups are allowable after the ``Transform`` has been -applied, Django uses the ``output_field`` attribute. We didn't need to specify -this here as it didn't change, but supposing we were applying ``AbsoluteValue`` -to some field which represents a more complex type (for example a point -relative to an origin, or a complex number) then we may have wanted to specify -``output_field = FloatField``, which will ensure that further lookups like -``abs__lte`` behave as they would for a ``FloatField``. - -Writing an efficient abs__lt lookup -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -When using the above written ``abs`` lookup, the SQL produced will not use -indexes efficiently in some cases. In particular, when we use -``change__abs__lt=27``, this is equivalent to ``change__gt=-27`` AND -``change__lt=27``. (For the ``lte`` case we could use the SQL ``BETWEEN``). - -So we would like ``Experiment.objects.filter(change__abs__lt=27)`` to generate -the following SQL:: - - SELECT .. WHERE "experiments"."change" < 27 AND "experiments"."change" > -27 - -The implementation is:: - - from django.db.models import Lookup - - class AbsoluteValueLessThan(Lookup): - lookup_name = 'lt' - - def as_sql(self, qn, connection): - lhs, lhs_params = qn.compile(self.lhs.lhs) - rhs, rhs_params = self.process_rhs(qn, connection) - params = lhs_params + rhs_params + lhs_params + rhs_params - return '%s < %s AND %s > -%s' % (lhs, rhs, lhs, rhs), params - - AbsoluteValue.register_lookup(AbsoluteValueLessThan) - -There are a couple of notable things going on. First, ``AbsoluteValueLessThan`` -isn't calling ``process_lhs()``. Instead it skips the transformation of the -``lhs`` done by ``AbsoluteValue`` and uses the original ``lhs``. That is, we -want to get ``27`` not ``ABS(27)``. Referring directly to ``self.lhs.lhs`` is -safe as ``AbsoluteValueLessThan`` can be accessed only from the -``AbsoluteValue`` lookup, that is the ``lhs`` is always an instance of -``AbsoluteValue``. - -Notice also that as both sides are used multiple times in the query the params -need to contain ``lhs_params`` and ``rhs_params`` multiple times. - -The final query does the inversion (``27`` to ``-27``) directly in the -database. The reason for doing this is that if the self.rhs is something else -than a plain integer value (for example an ``F()`` reference) we can't do the -transformations in Python. - -.. note:: - In fact, most lookups with ``__abs`` could be implemented as range queries - like this, and on most database backends it is likely to be more sensible to - do so as you can make use of the indexes. However with PostgreSQL you may - want to add an index on ``abs(change)`` which would allow these queries to - be very efficient. - -Writing alternative implementations for existing lookups -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Sometimes different database vendors require different SQL for the same -operation. For this example we will rewrite a custom implementation for -MySQL for the NotEqual operator. Instead of ``<>`` we will be using ``!=`` -operator. (Note that in reality almost all databases support both, including -all the official databases supported by Django). - -We can change the behavior on a specific backend by creating a subclass of -``NotEqual`` with a ``as_mysql`` method:: - - class MySQLNotEqual(NotEqual): - def as_mysql(self, qn, connection): - lhs, lhs_params = self.process_lhs(qn, connection) - rhs, rhs_params = self.process_rhs(qn, connection) - params = lhs_params + rhs_params - return '%s != %s' % (lhs, rhs), params - Field.register_lookup(MySQLNotExact) - -We can then register it with ``Field``. It takes the place of the original -``NotEqual`` class as it has the same ``lookup_name``. - -When compiling a query, Django first looks for ``as_%s % connection.vendor`` -methods, and then falls back to ``as_sql``. The vendor names for the in-built -backends are ``sqlite``, ``postgresql``, ``oracle`` and ``mysql``. - -How Django determines the lookups and transforms which are used -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -In some cases you may which to dynamically change which ``Transform`` or -``Lookup`` is returned based on the name passed in, rather than fixing it. As -an example, you could have a field which stores coordinates or an arbitrary -dimension, and wish to allow a syntax like ``.filter(coords__x7=4)`` to return -the objects where the 7th coordinate has value 4. In order to do this, you -would override ``get_lookup`` with something like:: - - class CoordinatesField(Field): - def get_lookup(self, lookup_name): - if lookup_name.startswith('x'): - try: - dimension = int(lookup_name[1:]) - except ValueError: - pass - finally: - return get_coordinate_lookup(dimension) - return super(CoordinatesField, self).get_lookup(lookup_name) - -You would then define ``get_coordinate_lookup`` appropriately to return a -``Lookup`` subclass which handles the relevant value of ``dimension``. - -There is a similarly named method called ``get_transform()``. ``get_lookup()`` -should always return a ``Lookup`` subclass, and ``get_transform()`` a -``Transform`` subclass. It is important to remember that ``Transform`` -objects can be further filtered on, and ``Lookup`` objects cannot. - -When filtering, if there is only one lookup name remaining to be resolved, we -will look for a ``Lookup``. If there are multiple names, it will look for a -``Transform``. In the situation where there is only one name and a ``Lookup`` -is not found, we look for a ``Transform`` and then the ``exact`` lookup on that -``Transform``. All call sequences always end with a ``Lookup``. To clarify: - -- ``.filter(myfield__mylookup)`` will call ``myfield.get_lookup('mylookup')``. -- ``.filter(myfield__mytransform__mylookup)`` will call - ``myfield.get_transform('mytransform')``, and then - ``mytransform.get_lookup('mylookup')``. -- ``.filter(myfield__mytransform)`` will first call - ``myfield.get_lookup('mytransform')``, which will fail, so it will fall back - to calling ``myfield.get_transform('mytransform')`` and then - ``mytransform.get_lookup('exact')``. - -Lookups and transforms are registered using the same API - ``register_lookup``. - -.. _query-expression: - -The Query Expression API -~~~~~~~~~~~~~~~~~~~~~~~~ - -A lookup can assume that the lhs responds to the query expression API. -Currently direct field references, aggregates and ``Transform`` instances respond -to this API. - -.. method:: as_sql(qn, connection) - - Responsible for producing the query string and parameters for the - expression. The ``qn`` is a ``SQLCompiler`` object, which has a - ``compile()`` method that can be used to compile other expressions. The - ``connection`` is the connection used to execute the query. - - Calling expression.as_sql() directly is usually incorrect - instead - ``qn.compile(expression)`` should be used. The ``qn.compile()`` method will - take care of calling vendor-specific methods of the expression. - -.. method:: as_vendorname(qn, connection) - - Works like ``as_sql()`` method. When an expression is compiled by - ``qn.compile()``, Django will first try to call ``as_vendorname()``, where - vendorname is the vendor name of the backend used for executing the query. - The vendorname is one of ``postgresql``, ``oracle``, ``sqlite`` or - ``mysql`` for Django's built-in backends. - -.. method:: get_lookup(lookup_name) - - The ``get_lookup()`` method is used to fetch lookups. By default the - lookup is fetched from the expression's output type in the same way - described in registering and fetching lookup documentation below. - It is possible to override this method to alter that behavior. - -.. method:: get_transform(lookup_name) - - The ``get_transform()`` method is used when a transform is needed rather - than a lookup, or if a lookup is not found. This is a more complex - situation which is useful when there arbitrary possible lookups for a - field. Generally speaking, you will not need to override ``get_lookup()`` - or ``get_transform()``, and can use ``register_lookup()`` instead. - -.. attribute:: output_field - - The ``output_field`` attribute is used by the ``get_lookup()`` method to - check for lookups. The ``output_field`` should be a field. - -Note that this documentation lists only the public methods of the API. - -Lookup reference -~~~~~~~~~~~~~~~~ - -.. class:: Lookup - - In addition to the attributes and methods below, lookups also support - ``as_sql`` and ``as_vendorname`` from the query expression API. - -.. attribute:: lhs - - The ``lhs`` (left-hand side) of a lookup tells us what we are comparing the - rhs to. It is an object which implements the query expression API. This is - likely to be a field, an aggregate or a subclass of ``Transform``. - -.. attribute:: rhs - - The ``rhs`` (right-hand side) of a lookup is the value we are comparing the - left hand side to. It may be a plain value, or something which compiles - into SQL, for example an ``F()`` object or a ``Queryset``. - -.. attribute:: lookup_name - - This class level attribute is used when registering lookups. It determines - the name used in queries to trigger this lookup. For example, ``contains`` - or ``exact``. This should not contain the string ``__``. - -.. method:: process_lhs(qn, connection) - - This returns a tuple of ``(lhs_string, lhs_params)``. In some cases you may - wish to compile ``lhs`` directly in your ``as_sql`` methods using - ``qn.compile(self.lhs)``. - -.. method:: process_rhs(qn, connection) - - Behaves the same as ``process_lhs`` but acts on the right-hand side. - -Transform reference -~~~~~~~~~~~~~~~~~~~ - -.. class:: Transform - - In addition to implementing the query expression API Transforms have the - following methods and attributes. - -.. attribute:: lhs - - The ``lhs`` (left-hand-side) of a transform contains the value to be - transformed. The ``lhs`` implements the query expression API. - -.. attribute:: lookup_name - - This class level attribute is used when registering lookups. It determines - the name used in queries to trigger this lookup. For example, ``year`` - or ``dayofweek``. This should not contain the string ``__``. - -.. _lookup-registration-api: - -Registering and fetching lookups -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The lookup registration API is explained below. - -.. classmethod:: register_lookup(lookup) - - Registers the Lookup or Transform for the class. For example - ``DateField.register_lookup(YearExact)`` will register ``YearExact`` for - all ``DateFields`` in the project, but also for fields that are instances - of a subclass of ``DateField`` (for example ``DateTimeField``). You can - register a Lookup or a Transform using the same class method. - -.. method:: get_lookup(lookup_name) - - Django uses ``get_lookup(lookup_name)`` to fetch lookups. The - implementation of ``get_lookup()`` looks for a subclass which is registered - for the current class with the correct ``lookup_name``. - -.. method:: get_transform(lookup_name) - - Django uses ``get_transform(lookup_name)`` to fetch transforms. The - implementation of ``get_transform()`` looks for a subclass which is registered - for the current class with the correct ``transform_name``. - -The lookup registration API is available for ``Transform`` and ``Field`` classes. diff --git a/docs/ref/models/index.txt b/docs/ref/models/index.txt index 4716c03f95..607ed9d314 100644 --- a/docs/ref/models/index.txt +++ b/docs/ref/models/index.txt @@ -13,4 +13,4 @@ Model API reference. For introductory material, see :doc:`/topics/db/models`. instances querysets queries - custom-lookups + lookups diff --git a/docs/ref/models/lookups.txt b/docs/ref/models/lookups.txt new file mode 100644 index 0000000000..ef331aa0ab --- /dev/null +++ b/docs/ref/models/lookups.txt @@ -0,0 +1,207 @@ +==================== +Lookup API reference +==================== + +.. module:: django.db.models.lookups + :synopsis: Lookups API + +.. currentmodule:: django.db.models + +.. versionadded:: 1.7 + +This document has the API references of lookups, the Django API for building +the ``WHERE`` clause of a database query. To learn how to *use* lookups, see +:doc:`/topics/db/queries`; to learn how to *create* new lookups, see +:doc:`/howto/custom-lookups`. + +The lookup API has two components: a :class:`~lookups.RegisterLookupMixin` class +that registers lookups, and the `Query Expression API <query-expression>`_, a +set of methods that a class has to implement to be registrable as a lookup. + +Django has two base classes that follow the query expression API and from where +all Django builtin lookups are derived: + +* :class:`Lookup`: to lookup a field (e.g. the ``exact`` of ``field_name__exact``) +* :class:`Transform`: to transform a field + +A lookup expression consists of three parts: + +* Fields part (e.g. ``Book.objects.filter(author__best_friends__first_name...``); +* Transforms part (may be omitted) (e.g. ``__lower__first3chars__reversed``); +* A lookup (e.g. ``__icontains``) that, if omitted, defaults to ``__exact``. + +.. _lookup-registration-api: + +Registration API +~~~~~~~~~~~~~~~~ + +Django uses :class:`~lookups.RegisterLookupMixin` to give a class the interface to +register lookups on itself. The two prominent examples are +:class:`~django.db.models.Field`, the base class of all model fields, and +``Aggregate``, the base class of all Django aggregates. + +.. class:: lookups.RegisterLookupMixin + + A mixin that implements the lookup API on a class. + + .. classmethod:: register_lookup(lookup) + + Registers a new lookup in the class. For example + ``DateField.register_lookup(YearExact)`` will register ``YearExact`` + lookup on ``DateField``. It overrides a lookup that already exists with + the same name. + + .. method:: get_lookup(lookup_name) + + Returns the :class:`Lookup` named ``lookup_name`` registered in the class. + The default implementation looks recursively on all parent classes + and checks if any has a registered lookup named ``lookup_name``, returning + the first match. + + .. method:: get_transform(transform_name) + + Returns a :class:`Transform` named ``transform_name``. The default + implementation looks recursively on all parent classes to check if any + has the registered transform named ``transform_name``, returning the first + match. + +For a class to be a lookup, it must follow the `Query Expression API +<query-expression>`_. :class:`~Lookup` and :class:`~Transform` naturally +follow this API. + +.. _query-expression: + +The Query Expression API +~~~~~~~~~~~~~~~~~~~~~~~~ + +The query expression API is a common set of methods that classes define to be +usable in query expressions to translate themselves into SQL expressions. Direct +field references, aggregates, and ``Transform`` are examples that follow this +API. A class is said to follow the query expression API when it implements the +following methods: + +.. method:: as_sql(self, qn, connection) + + Responsible for producing the query string and parameters for the expression. + The ``qn`` is an ``SQLCompiler`` object, which has a ``compile()`` method + that can be used to compile other expressions. The ``connection`` is the + connection used to execute the query. + + Calling ``expression.as_sql()`` is usually incorrect - instead + ``qn.compile(expression)`` should be used. The ``qn.compile()`` method will + take care of calling vendor-specific methods of the expression. + +.. method:: as_vendorname(self, qn, connection) + + Works like ``as_sql()`` method. When an expression is compiled by + ``qn.compile()``, Django will first try to call ``as_vendorname()``, where + ``vendorname`` is the vendor name of the backend used for executing the + query. The ``vendorname`` is one of ``postgresql``, ``oracle``, ``sqlite``, + or ``mysql`` for Django's built-in backends. + +.. method:: get_lookup(lookup_name) + + Must return the lookup named ``lookup_name``. For instance, by returning + ``self.output_field.get_lookup(lookup_name)``. + +.. method:: get_transform(transform_name) + + Must return the lookup named ``transform_name``. For instance, by returning + ``self.output_field.get_transform(transform_name)``. + +.. attribute:: output_field + + Defines the type of class returned by the ``get_lookup()`` method. It must + be a :class:`~django.db.models.Field` instance. + +Transform reference +~~~~~~~~~~~~~~~~~~~ + +.. class:: Transform + + A ``Transform`` is a generic class to implement field transformations. A + prominent example is ``__year`` that transforms a ``DateField`` into a + ``IntegerField``. + + The notation to use a ``Transform`` in an lookup expression is + ``<expression>__<transformation>`` (e.g. ``date__year``). + + This class follows the `Query Expression API <query-expression>`_, which + implies that you can use ``<expression>__<transform1>__<transform2>``. + + .. attribute:: lhs + + The left-hand side - what is being transformed. It must follow the + `Query Expression API <query-expression>`_. + + .. attribute:: lookup_name + + The name of the lookup, used for identifying it on parsing query + expressions. It cannot contain the string ``"__"``. + + .. attribute:: output_field + + Defines the class this transformation outputs. It must be a + :class:`~django.db.models.Field` instance. By default is the same as + its ``lhs.output_field``. + + .. method:: as_sql + + To be overridden; raises :exc:`NotImplementedError`. + + .. method:: get_lookup(lookup_name) + + Same as :meth:`~lookups.RegisterLookupMixin.get_lookup()`. + + .. method:: get_transform(transform_name) + + Same as :meth:`~lookups.RegisterLookupMixin.get_transform()`. + +Lookup reference +~~~~~~~~~~~~~~~~ + +.. class:: Lookup + + A ``Lookup`` is a generic class to implement lookups. A lookup is a query + expression with a left-hand side, :attr:`lhs`; a right-hand side, + :attr:`rhs`; and a ``lookup_name`` that is used to produce a boolean + comparison between ``lhs`` and ``rhs`` such as ``lhs in rhs`` or + ``lhs > rhs``. + + The notation to use a lookup in an expression is + ``<lhs>__<lookup_name>=<rhs>``. + + This class doesn't follow the `Query Expression API <query-expression>`_ + since it has ``=<rhs>`` on its construction: lookups are always the end of + a lookup expression. + + .. attribute:: lhs + + The left-hand side - what is being looked up. The object must follow + the `Query Expression API <query-expression>`_. + + .. attribute:: rhs + + The right-hand side - what ``lhs`` is being compared against. It can be + a plain value, or something that compiles into SQL, typically an + ``F()`` object or a ``QuerySet``. + + .. attribute:: lookup_name + + The name of this lookup, used to identify it on parsing query + expressions. It cannot contain the string ``"__"``. + + .. method:: process_lhs(qn, connection[, lhs=None]) + + Returns a tuple ``(lhs_string, lhs_params)``, as returned by + ``qn.compile(lhs)``. This method can be overridden to tune how the + ``lhs`` is processed. + + ``qn`` is an ``SQLCompiler`` object, to be used like ``qn.compile(lhs)`` + for compiling ``lhs``. The ``connection`` can be used for compiling + vendor specific SQL. If ``lhs`` is not ``None``, use it as the + processed ``lhs`` instead of ``self.lhs``. + + .. method:: process_rhs(qn, connection) + + Behaves the same way as :meth:`process_lhs`, for the right-hand side. diff --git a/docs/ref/models/querysets.txt b/docs/ref/models/querysets.txt index 6aa8b32a54..836c5ff536 100644 --- a/docs/ref/models/querysets.txt +++ b/docs/ref/models/querysets.txt @@ -2076,7 +2076,7 @@ For an introduction, see :ref:`models and database queries documentation <field-lookups-intro>`. Django's inbuilt lookups are listed below. It is also possible to write -:doc:`custom lookups </ref/models/custom-lookups>` for model fields. +:doc:`custom lookups </howto/custom-lookups>` for model fields. As a convenience when no lookup type is provided (like in ``Entry.objects.get(id=14)``) the lookup type is assumed to be :lookup:`exact`. |
