]> git.openstreetmap.org Git - nominatim.git/commitdiff
simplify handling of SQL lookup code for search_name
authorSarah Hoffmann <lonvia@denofr.de>
Wed, 6 Dec 2023 09:37:06 +0000 (10:37 +0100)
committerSarah Hoffmann <lonvia@denofr.de>
Thu, 7 Dec 2023 08:31:00 +0000 (09:31 +0100)
Use function classes which can be instantiated directly.

nominatim/api/search/db_search_builder.py
nominatim/api/search/db_search_fields.py
nominatim/api/search/db_search_lookups.py [new file with mode: 0644]
nominatim/api/search/db_searches.py
test/python/api/search/test_db_search_builder.py
test/python/api/search/test_search_near.py
test/python/api/search/test_search_places.py

index c755f2a74f8a16e2d53ca30503549040685d0046..fd8cc7af90ffb3aa71581aac842e602d82cc0d39 100644 (file)
@@ -15,6 +15,7 @@ from nominatim.api.search.query import QueryStruct, Token, TokenType, TokenRange
 from nominatim.api.search.token_assignment import TokenAssignment
 import nominatim.api.search.db_search_fields as dbf
 import nominatim.api.search.db_searches as dbs
+import nominatim.api.search.db_search_lookups as lookups
 
 
 def wrap_near_search(categories: List[Tuple[str, str]],
@@ -152,7 +153,7 @@ class SearchBuilder:
                 sdata.lookups = [dbf.FieldLookup('nameaddress_vector',
                                                  [t.token for r in address
                                                   for t in self.query.get_partials_list(r)],
-                                                 'restrict')]
+                                                 lookups.Restrict)]
                 penalty += 0.2
             yield dbs.PostcodeSearch(penalty, sdata)
 
@@ -162,7 +163,7 @@ class SearchBuilder:
         """ Build a simple address search for special entries where the
             housenumber is the main name token.
         """
-        sdata.lookups = [dbf.FieldLookup('name_vector', [t.token for t in hnrs], 'lookup_any')]
+        sdata.lookups = [dbf.FieldLookup('name_vector', [t.token for t in hnrs], lookups.LookupAny)]
         expected_count = sum(t.count for t in hnrs)
 
         partials = [t for trange in address
@@ -170,16 +171,16 @@ class SearchBuilder:
 
         if expected_count < 8000:
             sdata.lookups.append(dbf.FieldLookup('nameaddress_vector',
-                                                 [t.token for t in partials], 'restrict'))
+                                                 [t.token for t in partials], lookups.Restrict))
         elif len(partials) != 1 or partials[0].count < 10000:
             sdata.lookups.append(dbf.FieldLookup('nameaddress_vector',
-                                                 [t.token for t in partials], 'lookup_all'))
+                                                 [t.token for t in partials], lookups.LookupAll))
         else:
             sdata.lookups.append(
                 dbf.FieldLookup('nameaddress_vector',
                                 [t.token for t
                                  in self.query.get_tokens(address[0], TokenType.WORD)],
-                                'lookup_any'))
+                                lookups.LookupAny))
 
         sdata.housenumbers = dbf.WeightedStrings([], [])
         yield dbs.PlaceSearch(0.05, sdata, expected_count)
@@ -232,16 +233,16 @@ class SearchBuilder:
                 penalty += 1.2 * sum(t.penalty for t in addr_partials if not t.is_indexed)
             # Any of the full names applies with all of the partials from the address
             yield penalty, fulls_count / (2**len(addr_partials)),\
-                  dbf.lookup_by_any_name([t.token for t in name_fulls], addr_tokens,
-                                         'restrict' if fulls_count < 10000 else 'lookup_all')
+                  dbf.lookup_by_any_name([t.token for t in name_fulls],
+                                         addr_tokens, fulls_count > 10000)
 
         # To catch remaining results, lookup by name and address
         # We only do this if there is a reasonable number of results expected.
         exp_count = exp_count / (2**len(addr_partials)) if addr_partials else exp_count
         if exp_count < 10000 and all(t.is_indexed for t in name_partials):
-            lookup = [dbf.FieldLookup('name_vector', name_tokens, 'lookup_all')]
+            lookup = [dbf.FieldLookup('name_vector', name_tokens, lookups.LookupAll)]
             if addr_tokens:
-                lookup.append(dbf.FieldLookup('nameaddress_vector', addr_tokens, 'lookup_all'))
+                lookup.append(dbf.FieldLookup('nameaddress_vector', addr_tokens, lookups.LookupAll))
             penalty += 0.35 * max(0, 5 - len(name_partials) - len(addr_tokens))
             yield penalty, exp_count, lookup
 
index 324a7acc2cafe5a553dc60fdb6f5ca1b948568ae..6947a565f80dad421dcd9398975284988121a254 100644 (file)
@@ -7,15 +7,17 @@
 """
 Data structures for more complex fields in abstract search descriptions.
 """
-from typing import List, Tuple, Iterator, cast, Dict
+from typing import List, Tuple, Iterator, Dict, Type
 import dataclasses
 
 import sqlalchemy as sa
 
 from nominatim.typing import SaFromClause, SaColumn, SaExpression
 from nominatim.api.search.query import Token
+import nominatim.api.search.db_search_lookups as lookups
 from nominatim.utils.json_writer import JsonWriter
 
+
 @dataclasses.dataclass
 class WeightedStrings:
     """ A list of strings together with a penalty.
@@ -152,18 +154,12 @@ class FieldLookup:
     """
     column: str
     tokens: List[int]
-    lookup_type: str
+    lookup_type: Type[lookups.LookupType]
 
     def sql_condition(self, table: SaFromClause) -> SaColumn:
         """ Create an SQL expression for the given match condition.
         """
-        col = table.c[self.column]
-        if self.lookup_type == 'lookup_all':
-            return col.contains(self.tokens)
-        if self.lookup_type == 'lookup_any':
-            return cast(SaColumn, col.overlaps(self.tokens))
-
-        return sa.func.coalesce(sa.null(), col).contains(self.tokens) # pylint: disable=not-callable
+        return self.lookup_type(table, self.column, self.tokens)
 
 
 class SearchData:
@@ -229,22 +225,23 @@ def lookup_by_names(name_tokens: List[int], addr_tokens: List[int]) -> List[Fiel
     """ Create a lookup list where name tokens are looked up via index
         and potential address tokens are used to restrict the search further.
     """
-    lookup = [FieldLookup('name_vector', name_tokens, 'lookup_all')]
+    lookup = [FieldLookup('name_vector', name_tokens, lookups.LookupAll)]
     if addr_tokens:
-        lookup.append(FieldLookup('nameaddress_vector', addr_tokens, 'restrict'))
+        lookup.append(FieldLookup('nameaddress_vector', addr_tokens, lookups.Restrict))
 
     return lookup
 
 
 def lookup_by_any_name(name_tokens: List[int], addr_tokens: List[int],
-                       lookup_type: str) -> List[FieldLookup]:
+                       use_index_for_addr: bool) -> List[FieldLookup]:
     """ Create a lookup list where name tokens are looked up via index
         and only one of the name tokens must be present.
         Potential address tokens are used to restrict the search further.
     """
-    lookup = [FieldLookup('name_vector', name_tokens, 'lookup_any')]
+    lookup = [FieldLookup('name_vector', name_tokens, lookups.LookupAny)]
     if addr_tokens:
-        lookup.append(FieldLookup('nameaddress_vector', addr_tokens, lookup_type))
+        lookup.append(FieldLookup('nameaddress_vector', addr_tokens,
+                                  lookups.LookupAll if use_index_for_addr else lookups.Restrict))
 
     return lookup
 
@@ -253,5 +250,5 @@ def lookup_by_addr(name_tokens: List[int], addr_tokens: List[int]) -> List[Field
     """ Create a lookup list where address tokens are looked up via index
         and the name tokens are only used to restrict the search further.
     """
-    return [FieldLookup('name_vector', name_tokens, 'restrict'),
-            FieldLookup('nameaddress_vector', addr_tokens, 'lookup_all')]
+    return [FieldLookup('name_vector', name_tokens, lookups.Restrict),
+            FieldLookup('nameaddress_vector', addr_tokens, lookups.LookupAll)]
diff --git a/nominatim/api/search/db_search_lookups.py b/nominatim/api/search/db_search_lookups.py
new file mode 100644 (file)
index 0000000..3e30723
--- /dev/null
@@ -0,0 +1,78 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+# This file is part of Nominatim. (https://nominatim.org)
+#
+# Copyright (C) 2023 by the Nominatim developer community.
+# For a full list of authors see the git log.
+"""
+Implementation of lookup functions for the search_name table.
+"""
+from typing import List, Any
+
+import sqlalchemy as sa
+from sqlalchemy.ext.compiler import compiles
+
+from nominatim.typing import SaFromClause
+from nominatim.db.sqlalchemy_types import IntArray
+
+# pylint: disable=consider-using-f-string
+
+LookupType = sa.sql.expression.FunctionElement[Any]
+
+class LookupAll(LookupType):
+    """ Find all entries in search_name table that contain all of
+        a given list of tokens using an index for the search.
+    """
+    inherit_cache = True
+
+    def __init__(self, table: SaFromClause, column: str, tokens: List[int]) -> None:
+        super().__init__(getattr(table.c, column),
+                         sa.type_coerce(tokens, IntArray))
+
+
+@compiles(LookupAll) # type: ignore[no-untyped-call, misc]
+def _default_lookup_all(element: LookupAll,
+                        compiler: 'sa.Compiled', **kw: Any) -> str:
+    col, tokens = list(element.clauses)
+    return "(%s @> %s)" % (compiler.process(col, **kw),
+                           compiler.process(tokens, **kw))
+
+
+
+class LookupAny(LookupType):
+    """ Find all entries that contain at least one of the given tokens.
+        Use an index for the search.
+    """
+    inherit_cache = True
+
+    def __init__(self, table: SaFromClause, column: str, tokens: List[int]) -> None:
+        super().__init__(getattr(table.c, column),
+                         sa.type_coerce(tokens, IntArray))
+
+
+@compiles(LookupAny) # type: ignore[no-untyped-call, misc]
+def _default_lookup_any(element: LookupAny,
+                        compiler: 'sa.Compiled', **kw: Any) -> str:
+    col, tokens = list(element.clauses)
+    return "(%s && %s)" % (compiler.process(col, **kw),
+                           compiler.process(tokens, **kw))
+
+
+
+class Restrict(LookupType):
+    """ Find all entries that contain all of the given tokens.
+        Do not use an index for the search.
+    """
+    inherit_cache = True
+
+    def __init__(self, table: SaFromClause, column: str, tokens: List[int]) -> None:
+        super().__init__(getattr(table.c, column),
+                         sa.type_coerce(tokens, IntArray))
+
+
+@compiles(Restrict) # type: ignore[no-untyped-call, misc]
+def _default_restrict(element: Restrict,
+                        compiler: 'sa.Compiled', **kw: Any) -> str:
+    arg1, arg2 = list(element.clauses)
+    return "(coalesce(null, %s) @> %s)" % (compiler.process(arg1, **kw),
+                                           compiler.process(arg2, **kw))
index 48bd6272c807de60edd454202fbab1d1540f9c09..35c12746597b1726a0d6531fab45a357b65e5c4c 100644 (file)
@@ -563,7 +563,6 @@ class PostcodeSearch(AbstractSearch):
 
         if self.lookups:
             assert len(self.lookups) == 1
-            assert self.lookups[0].lookup_type == 'restrict'
             tsearch = conn.t.search_name
             sql = sql.where(tsearch.c.place_id == t.c.parent_place_id)\
                      .where((tsearch.c.name_vector + tsearch.c.nameaddress_vector)
index 87d75261528283574aae5d6a83b09d5645ac406e..d3aea90002740d7660e12a4b210bf4cb41344c60 100644 (file)
@@ -420,8 +420,8 @@ def test_infrequent_partials_in_name():
     assert len(search.lookups) == 2
     assert len(search.rankings) == 2
 
-    assert set((l.column, l.lookup_type) for l in search.lookups) == \
-            {('name_vector', 'lookup_all'), ('nameaddress_vector', 'restrict')}
+    assert set((l.column, l.lookup_type.__name__) for l in search.lookups) == \
+            {('name_vector', 'LookupAll'), ('nameaddress_vector', 'Restrict')}
 
 
 def test_frequent_partials_in_name_and_address():
@@ -432,10 +432,10 @@ def test_frequent_partials_in_name_and_address():
     assert all(isinstance(s, dbs.PlaceSearch) for s in searches)
     searches.sort(key=lambda s: s.penalty)
 
-    assert set((l.column, l.lookup_type) for l in searches[0].lookups) == \
-            {('name_vector', 'lookup_any'), ('nameaddress_vector', 'restrict')}
-    assert set((l.column, l.lookup_type) for l in searches[1].lookups) == \
-            {('nameaddress_vector', 'lookup_all'), ('name_vector', 'lookup_all')}
+    assert set((l.column, l.lookup_type.__name__) for l in searches[0].lookups) == \
+            {('name_vector', 'LookupAny'), ('nameaddress_vector', 'Restrict')}
+    assert set((l.column, l.lookup_type.__name__) for l in searches[1].lookups) == \
+            {('nameaddress_vector', 'LookupAll'), ('name_vector', 'LookupAll')}
 
 
 def test_too_frequent_partials_in_name_and_address():
@@ -446,5 +446,5 @@ def test_too_frequent_partials_in_name_and_address():
     assert all(isinstance(s, dbs.PlaceSearch) for s in searches)
     searches.sort(key=lambda s: s.penalty)
 
-    assert set((l.column, l.lookup_type) for l in searches[0].lookups) == \
-            {('name_vector', 'lookup_any'), ('nameaddress_vector', 'restrict')}
+    assert set((l.column, l.lookup_type.__name__) for l in searches[0].lookups) == \
+            {('name_vector', 'LookupAny'), ('nameaddress_vector', 'Restrict')}
index 2a0acb745969a777a75856f8cc002ea7e33da91f..c0caa9ae6af336a2be15599ce02dbe489df98e6d 100644 (file)
@@ -14,6 +14,7 @@ from nominatim.api.types import SearchDetails
 from nominatim.api.search.db_searches import NearSearch, PlaceSearch
 from nominatim.api.search.db_search_fields import WeightedStrings, WeightedCategories,\
                                                   FieldLookup, FieldRanking, RankedTokens
+from nominatim.api.search.db_search_lookups import LookupAll
 
 
 def run_search(apiobj, global_penalty, cat, cat_penalty=None, ccodes=[],
@@ -25,7 +26,7 @@ def run_search(apiobj, global_penalty, cat, cat_penalty=None, ccodes=[],
         countries = WeightedStrings(ccodes, [0.0] * len(ccodes))
         housenumbers = WeightedStrings([], [])
         qualifiers = WeightedStrings([], [])
-        lookups = [FieldLookup('name_vector', [56], 'lookup_all')]
+        lookups = [FieldLookup('name_vector', [56], LookupAll)]
         rankings = []
 
     if ccodes is not None:
index 8a363e97735b585aee1372ea6d87d05a3a12a17e..44e4098dada62713b87816246ea5929778030cce 100644 (file)
@@ -16,6 +16,7 @@ from nominatim.api.types import SearchDetails
 from nominatim.api.search.db_searches import PlaceSearch
 from nominatim.api.search.db_search_fields import WeightedStrings, WeightedCategories,\
                                                   FieldLookup, FieldRanking, RankedTokens
+from nominatim.api.search.db_search_lookups import LookupAll, LookupAny, Restrict
 
 def run_search(apiobj, global_penalty, lookup, ranking, count=2,
                hnrs=[], pcs=[], ccodes=[], quals=[],
@@ -55,7 +56,7 @@ class TestNameOnlySearches:
                                centroid=(-10.3, 56.9))
 
 
-    @pytest.mark.parametrize('lookup_type', ['lookup_all', 'restrict'])
+    @pytest.mark.parametrize('lookup_type', [LookupAll, Restrict])
     @pytest.mark.parametrize('rank,res', [([10], [100, 101]),
                                           ([20], [101, 100])])
     def test_lookup_all_match(self, apiobj, lookup_type, rank, res):
@@ -67,7 +68,7 @@ class TestNameOnlySearches:
         assert [r.place_id for r in results] == res
 
 
-    @pytest.mark.parametrize('lookup_type', ['lookup_all', 'restrict'])
+    @pytest.mark.parametrize('lookup_type', [LookupAll, Restrict])
     def test_lookup_all_partial_match(self, apiobj, lookup_type):
         lookup = FieldLookup('name_vector', [1,20], lookup_type)
         ranking = FieldRanking('name_vector', 0.9, [RankedTokens(0.0, [21])])
@@ -80,7 +81,7 @@ class TestNameOnlySearches:
     @pytest.mark.parametrize('rank,res', [([10], [100, 101]),
                                           ([20], [101, 100])])
     def test_lookup_any_match(self, apiobj, rank, res):
-        lookup = FieldLookup('name_vector', [11,21], 'lookup_any')
+        lookup = FieldLookup('name_vector', [11,21], LookupAny)
         ranking = FieldRanking('name_vector', 0.9, [RankedTokens(0.0, rank)])
 
         results = run_search(apiobj, 0.1, [lookup], [ranking])
@@ -89,7 +90,7 @@ class TestNameOnlySearches:
 
 
     def test_lookup_any_partial_match(self, apiobj):
-        lookup = FieldLookup('name_vector', [20], 'lookup_all')
+        lookup = FieldLookup('name_vector', [20], LookupAll)
         ranking = FieldRanking('name_vector', 0.9, [RankedTokens(0.0, [21])])
 
         results = run_search(apiobj, 0.1, [lookup], [ranking])
@@ -100,7 +101,7 @@ class TestNameOnlySearches:
 
     @pytest.mark.parametrize('cc,res', [('us', 100), ('mx', 101)])
     def test_lookup_restrict_country(self, apiobj, cc, res):
-        lookup = FieldLookup('name_vector', [1,2], 'lookup_all')
+        lookup = FieldLookup('name_vector', [1,2], LookupAll)
         ranking = FieldRanking('name_vector', 0.9, [RankedTokens(0.0, [10])])
 
         results = run_search(apiobj, 0.1, [lookup], [ranking], ccodes=[cc])
@@ -109,7 +110,7 @@ class TestNameOnlySearches:
 
 
     def test_lookup_restrict_placeid(self, apiobj):
-        lookup = FieldLookup('name_vector', [1,2], 'lookup_all')
+        lookup = FieldLookup('name_vector', [1,2], LookupAll)
         ranking = FieldRanking('name_vector', 0.9, [RankedTokens(0.0, [10])])
 
         results = run_search(apiobj, 0.1, [lookup], [ranking],
@@ -123,7 +124,7 @@ class TestNameOnlySearches:
                                       napi.GeometryFormat.SVG,
                                       napi.GeometryFormat.TEXT])
     def test_return_geometries(self, apiobj, geom):
-        lookup = FieldLookup('name_vector', [20], 'lookup_all')
+        lookup = FieldLookup('name_vector', [20], LookupAll)
         ranking = FieldRanking('name_vector', 0.9, [RankedTokens(0.0, [21])])
 
         results = run_search(apiobj, 0.1, [lookup], [ranking],
@@ -140,7 +141,7 @@ class TestNameOnlySearches:
         apiobj.add_search_name(333, names=[55], country_code='us',
                                centroid=(5.6, 4.3))
 
-        lookup = FieldLookup('name_vector', [55], 'lookup_all')
+        lookup = FieldLookup('name_vector', [55], LookupAll)
         ranking = FieldRanking('name_vector', 0.9, [RankedTokens(0.0, [21])])
 
         results = run_search(apiobj, 0.1, [lookup], [ranking],
@@ -158,7 +159,7 @@ class TestNameOnlySearches:
     @pytest.mark.parametrize('viewbox', ['5.0,4.0,6.0,5.0', '5.7,4.0,6.0,5.0'])
     @pytest.mark.parametrize('wcount,rids', [(2, [100, 101]), (20000, [100])])
     def test_prefer_viewbox(self, apiobj, viewbox, wcount, rids):
-        lookup = FieldLookup('name_vector', [1, 2], 'lookup_all')
+        lookup = FieldLookup('name_vector', [1, 2], LookupAll)
         ranking = FieldRanking('name_vector', 0.2, [RankedTokens(0.0, [21])])
 
         results = run_search(apiobj, 0.1, [lookup], [ranking])
@@ -171,7 +172,7 @@ class TestNameOnlySearches:
 
     @pytest.mark.parametrize('viewbox', ['5.0,4.0,6.0,5.0', '5.55,4.27,5.62,4.31'])
     def test_force_viewbox(self, apiobj, viewbox):
-        lookup = FieldLookup('name_vector', [1, 2], 'lookup_all')
+        lookup = FieldLookup('name_vector', [1, 2], LookupAll)
 
         details=SearchDetails.from_kwargs({'viewbox': viewbox,
                                            'bounded_viewbox': True})
@@ -181,7 +182,7 @@ class TestNameOnlySearches:
 
 
     def test_prefer_near(self, apiobj):
-        lookup = FieldLookup('name_vector', [1, 2], 'lookup_all')
+        lookup = FieldLookup('name_vector', [1, 2], LookupAll)
         ranking = FieldRanking('name_vector', 0.9, [RankedTokens(0.0, [21])])
 
         results = run_search(apiobj, 0.1, [lookup], [ranking])
@@ -195,7 +196,7 @@ class TestNameOnlySearches:
 
     @pytest.mark.parametrize('radius', [0.09, 0.11])
     def test_force_near(self, apiobj, radius):
-        lookup = FieldLookup('name_vector', [1, 2], 'lookup_all')
+        lookup = FieldLookup('name_vector', [1, 2], LookupAll)
 
         details=SearchDetails.from_kwargs({'near': '5.6,4.3',
                                            'near_radius': radius})
@@ -242,7 +243,7 @@ class TestStreetWithHousenumber:
                                          ('21', [2]), ('22', [2, 92]),
                                          ('24', [93]), ('25', [])])
     def test_lookup_by_single_housenumber(self, apiobj, hnr, res):
-        lookup = FieldLookup('name_vector', [1,2], 'lookup_all')
+        lookup = FieldLookup('name_vector', [1,2], LookupAll)
         ranking = FieldRanking('name_vector', 0.3, [RankedTokens(0.0, [10])])
 
         results = run_search(apiobj, 0.1, [lookup], [ranking], hnrs=[hnr])
@@ -252,7 +253,7 @@ class TestStreetWithHousenumber:
 
     @pytest.mark.parametrize('cc,res', [('es', [2, 1000]), ('pt', [92, 2000])])
     def test_lookup_with_country_restriction(self, apiobj, cc, res):
-        lookup = FieldLookup('name_vector', [1,2], 'lookup_all')
+        lookup = FieldLookup('name_vector', [1,2], LookupAll)
         ranking = FieldRanking('name_vector', 0.3, [RankedTokens(0.0, [10])])
 
         results = run_search(apiobj, 0.1, [lookup], [ranking], hnrs=['22'],
@@ -262,7 +263,7 @@ class TestStreetWithHousenumber:
 
 
     def test_lookup_exclude_housenumber_placeid(self, apiobj):
-        lookup = FieldLookup('name_vector', [1,2], 'lookup_all')
+        lookup = FieldLookup('name_vector', [1,2], LookupAll)
         ranking = FieldRanking('name_vector', 0.3, [RankedTokens(0.0, [10])])
 
         results = run_search(apiobj, 0.1, [lookup], [ranking], hnrs=['22'],
@@ -272,7 +273,7 @@ class TestStreetWithHousenumber:
 
 
     def test_lookup_exclude_street_placeid(self, apiobj):
-        lookup = FieldLookup('name_vector', [1,2], 'lookup_all')
+        lookup = FieldLookup('name_vector', [1,2], LookupAll)
         ranking = FieldRanking('name_vector', 0.3, [RankedTokens(0.0, [10])])
 
         results = run_search(apiobj, 0.1, [lookup], [ranking], hnrs=['22'],
@@ -282,7 +283,7 @@ class TestStreetWithHousenumber:
 
 
     def test_lookup_only_house_qualifier(self, apiobj):
-        lookup = FieldLookup('name_vector', [1,2], 'lookup_all')
+        lookup = FieldLookup('name_vector', [1,2], LookupAll)
         ranking = FieldRanking('name_vector', 0.3, [RankedTokens(0.0, [10])])
 
         results = run_search(apiobj, 0.1, [lookup], [ranking], hnrs=['22'],
@@ -292,7 +293,7 @@ class TestStreetWithHousenumber:
 
 
     def test_lookup_only_street_qualifier(self, apiobj):
-        lookup = FieldLookup('name_vector', [1,2], 'lookup_all')
+        lookup = FieldLookup('name_vector', [1,2], LookupAll)
         ranking = FieldRanking('name_vector', 0.3, [RankedTokens(0.0, [10])])
 
         results = run_search(apiobj, 0.1, [lookup], [ranking], hnrs=['22'],
@@ -303,7 +304,7 @@ class TestStreetWithHousenumber:
 
     @pytest.mark.parametrize('rank,found', [(26, True), (27, False), (30, False)])
     def test_lookup_min_rank(self, apiobj, rank, found):
-        lookup = FieldLookup('name_vector', [1,2], 'lookup_all')
+        lookup = FieldLookup('name_vector', [1,2], LookupAll)
         ranking = FieldRanking('name_vector', 0.3, [RankedTokens(0.0, [10])])
 
         results = run_search(apiobj, 0.1, [lookup], [ranking], hnrs=['22'],
@@ -317,7 +318,7 @@ class TestStreetWithHousenumber:
                                       napi.GeometryFormat.SVG,
                                       napi.GeometryFormat.TEXT])
     def test_return_geometries(self, apiobj, geom):
-        lookup = FieldLookup('name_vector', [1, 2], 'lookup_all')
+        lookup = FieldLookup('name_vector', [1, 2], LookupAll)
 
         results = run_search(apiobj, 0.1, [lookup], [], hnrs=['20', '21', '22'],
                              details=SearchDetails(geometry_output=geom))
@@ -337,7 +338,7 @@ def test_very_large_housenumber(apiobj):
                            search_rank=26, address_rank=26,
                            country_code='pt')
 
-    lookup = FieldLookup('name_vector', [1, 2], 'lookup_all')
+    lookup = FieldLookup('name_vector', [1, 2], LookupAll)
 
     results = run_search(apiobj, 0.1, [lookup], [], hnrs=['2467463524544'],
                          details=SearchDetails())
@@ -365,7 +366,7 @@ def test_name_and_postcode(apiobj, wcount, rids):
     apiobj.add_postcode(place_id=100, country_code='ch', postcode='11225',
                         geometry='POINT(10 10)')
 
-    lookup = FieldLookup('name_vector', [111], 'lookup_all')
+    lookup = FieldLookup('name_vector', [111], LookupAll)
 
     results = run_search(apiobj, 0.1, [lookup], [], pcs=['11225'], count=wcount,
                          details=SearchDetails())
@@ -398,7 +399,7 @@ class TestInterpolations:
 
     @pytest.mark.parametrize('hnr,res', [('21', [992]), ('22', []), ('23', [991])])
     def test_lookup_housenumber(self, apiobj, hnr, res):
-        lookup = FieldLookup('name_vector', [111], 'lookup_all')
+        lookup = FieldLookup('name_vector', [111], LookupAll)
 
         results = run_search(apiobj, 0.1, [lookup], [], hnrs=[hnr])
 
@@ -410,7 +411,7 @@ class TestInterpolations:
                                       napi.GeometryFormat.SVG,
                                       napi.GeometryFormat.TEXT])
     def test_osmline_with_geometries(self, apiobj, geom):
-        lookup = FieldLookup('name_vector', [111], 'lookup_all')
+        lookup = FieldLookup('name_vector', [111], LookupAll)
 
         results = run_search(apiobj, 0.1, [lookup], [], hnrs=['21'],
                              details=SearchDetails(geometry_output=geom))
@@ -446,7 +447,7 @@ class TestTiger:
 
     @pytest.mark.parametrize('hnr,res', [('21', [992]), ('22', []), ('23', [991])])
     def test_lookup_housenumber(self, apiobj, hnr, res):
-        lookup = FieldLookup('name_vector', [111], 'lookup_all')
+        lookup = FieldLookup('name_vector', [111], LookupAll)
 
         results = run_search(apiobj, 0.1, [lookup], [], hnrs=[hnr])
 
@@ -458,7 +459,7 @@ class TestTiger:
                                       napi.GeometryFormat.SVG,
                                       napi.GeometryFormat.TEXT])
     def test_tiger_with_geometries(self, apiobj, geom):
-        lookup = FieldLookup('name_vector', [111], 'lookup_all')
+        lookup = FieldLookup('name_vector', [111], LookupAll)
 
         results = run_search(apiobj, 0.1, [lookup], [], hnrs=['21'],
                              details=SearchDetails(geometry_output=geom))
@@ -513,7 +514,7 @@ class TestLayersRank30:
                                            (napi.DataLayer.MANMADE | napi.DataLayer.NATURAL, [225, 227]),
                                            (napi.DataLayer.MANMADE | napi.DataLayer.RAILWAY, [225, 226])])
     def test_layers_rank30(self, apiobj, layer, res):
-        lookup = FieldLookup('name_vector', [34], 'lookup_any')
+        lookup = FieldLookup('name_vector', [34], LookupAny)
 
         results = run_search(apiobj, 0.1, [lookup], [],
                              details=SearchDetails(layers=layer))