]> git.openstreetmap.org Git - nominatim.git/blob - nominatim/tokenizer/base.py
Merge pull request #3172 from lonvia/query-timeout
[nominatim.git] / nominatim / tokenizer / base.py
1 # SPDX-License-Identifier: GPL-2.0-only
2 #
3 # This file is part of Nominatim. (https://nominatim.org)
4 #
5 # Copyright (C) 2022 by the Nominatim developer community.
6 # For a full list of authors see the git log.
7 """
8 Abstract class definitions for tokenizers. These base classes are here
9 mainly for documentation purposes.
10 """
11 from abc import ABC, abstractmethod
12 from typing import List, Tuple, Dict, Any, Optional, Iterable
13 from pathlib import Path
14
15 from nominatim.config import Configuration
16 from nominatim.db.connection import Connection
17 from nominatim.data.place_info import PlaceInfo
18 from nominatim.typing import Protocol
19
20 class AbstractAnalyzer(ABC):
21     """ The analyzer provides the functions for analysing names and building
22         the token database.
23
24         Analyzers are instantiated on a per-thread base. Access to global data
25         structures must be synchronised accordingly.
26     """
27
28     def __enter__(self) -> 'AbstractAnalyzer':
29         return self
30
31
32     def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None:
33         self.close()
34
35
36     @abstractmethod
37     def close(self) -> None:
38         """ Free all resources used by the analyzer.
39         """
40
41
42     @abstractmethod
43     def get_word_token_info(self, words: List[str]) -> List[Tuple[str, str, int]]:
44         """ Return token information for the given list of words.
45
46             The function is used for testing and debugging only
47             and does not need to be particularly efficient.
48
49             Arguments:
50                 words: A list of words to look up the tokens for.
51                        If a word starts with # it is assumed to be a full name
52                        otherwise is a partial term.
53
54             Returns:
55                 The function returns the list of all tuples that could be
56                 found for the given words. Each list entry is a tuple of
57                 (original word, word token, word id).
58         """
59
60
61     @abstractmethod
62     def normalize_postcode(self, postcode: str) -> str:
63         """ Convert the postcode to its standardized form.
64
65             This function must yield exactly the same result as the SQL function
66             `token_normalized_postcode()`.
67
68             Arguments:
69                 postcode: The postcode to be normalized.
70
71             Returns:
72                 The given postcode after normalization.
73         """
74
75
76     @abstractmethod
77     def update_postcodes_from_db(self) -> None:
78         """ Update the tokenizer's postcode tokens from the current content
79             of the `location_postcode` table.
80         """
81
82
83     @abstractmethod
84     def update_special_phrases(self,
85                                phrases: Iterable[Tuple[str, str, str, str]],
86                                should_replace: bool) -> None:
87         """ Update the tokenizer's special phrase tokens from the given
88             list of special phrases.
89
90             Arguments:
91                 phrases: The new list of special phrases. Each entry is
92                          a tuple of (phrase, class, type, operator).
93                 should_replace: If true, replace the current list of phrases.
94                                 When false, just add the given phrases to the
95                                 ones that already exist.
96         """
97
98
99     @abstractmethod
100     def add_country_names(self, country_code: str, names: Dict[str, str]) -> None:
101         """ Add the given names to the tokenizer's list of country tokens.
102
103             Arguments:
104                 country_code: two-letter country code for the country the names
105                               refer to.
106                 names: Dictionary of name type to name.
107         """
108
109
110     @abstractmethod
111     def process_place(self, place: PlaceInfo) -> Any:
112         """ Extract tokens for the given place and compute the
113             information to be handed to the PL/pgSQL processor for building
114             the search index.
115
116             Arguments:
117                 place: Place information retrieved from the database.
118
119             Returns:
120                 A JSON-serialisable structure that will be handed into
121                 the database via the `token_info` field.
122         """
123
124
125
126 class AbstractTokenizer(ABC):
127     """ The tokenizer instance is the central instance of the tokenizer in
128         the system. There will only be a single instance of the tokenizer
129         active at any time.
130     """
131
132     @abstractmethod
133     def init_new_db(self, config: Configuration, init_db: bool = True) -> None:
134         """ Set up a new tokenizer for the database.
135
136             The function should copy all necessary data into the project
137             directory or save it in the property table to make sure that
138             the tokenizer remains stable over updates.
139
140             Arguments:
141               config: Read-only object with configuration options.
142
143               init_db: When set to False, then initialisation of database
144                 tables should be skipped. This option is only required for
145                 migration purposes and can be safely ignored by custom
146                 tokenizers.
147
148             TODO: can we move the init_db parameter somewhere else?
149         """
150
151
152     @abstractmethod
153     def init_from_project(self, config: Configuration) -> None:
154         """ Initialise the tokenizer from an existing database setup.
155
156             The function should load all previously saved configuration from
157             the project directory and/or the property table.
158
159             Arguments:
160               config: Read-only object with configuration options.
161         """
162
163
164     @abstractmethod
165     def finalize_import(self, config: Configuration) -> None:
166         """ This function is called at the very end of an import when all
167             data has been imported and indexed. The tokenizer may create
168             at this point any additional indexes and data structures needed
169             during query time.
170
171             Arguments:
172               config: Read-only object with configuration options.
173         """
174
175
176     @abstractmethod
177     def update_sql_functions(self, config: Configuration) -> None:
178         """ Update the SQL part of the tokenizer. This function is called
179             automatically on migrations or may be called explicitly by the
180             user through the `nominatim refresh --functions` command.
181
182             The tokenizer must only update the code of the tokenizer. The
183             data structures or data itself must not be changed by this function.
184
185             Arguments:
186               config: Read-only object with configuration options.
187         """
188
189
190     @abstractmethod
191     def check_database(self, config: Configuration) -> Optional[str]:
192         """ Check that the database is set up correctly and ready for being
193             queried.
194
195             Arguments:
196               config: Read-only object with configuration options.
197
198             Returns:
199               If an issue was found, return an error message with the
200               description of the issue as well as hints for the user on
201               how to resolve the issue. If everything is okay, return `None`.
202         """
203
204
205     @abstractmethod
206     def update_statistics(self) -> None:
207         """ Recompute any tokenizer statistics necessary for efficient lookup.
208             This function is meant to be called from time to time by the user
209             to improve performance. However, the tokenizer must not depend on
210             it to be called in order to work.
211         """
212
213
214     @abstractmethod
215     def update_word_tokens(self) -> None:
216         """ Do house-keeping on the tokenizers internal data structures.
217             Remove unused word tokens, resort data etc.
218         """
219
220
221     @abstractmethod
222     def name_analyzer(self) -> AbstractAnalyzer:
223         """ Create a new analyzer for tokenizing names and queries
224             using this tokinzer. Analyzers are context managers and should
225             be used accordingly:
226
227             ```
228             with tokenizer.name_analyzer() as analyzer:
229                 analyser.tokenize()
230             ```
231
232             When used outside the with construct, the caller must ensure to
233             call the close() function before destructing the analyzer.
234         """
235
236
237     @abstractmethod
238     def most_frequent_words(self, conn: Connection, num: int) -> List[str]:
239         """ Return a list of the `num` most frequent full words
240             in the database.
241         """
242
243
244 class TokenizerModule(Protocol):
245     """ Interface that must be exported by modules that implement their
246         own tokenizer.
247     """
248
249     def create(self, dsn: str, data_dir: Path) -> AbstractTokenizer:
250         """ Factory for new tokenizers.
251         """