3 The tokenizer is the component of Nominatim that is responsible for
 
   4 analysing names of OSM objects and queries. Nominatim provides different
 
   5 tokenizers that use different strategies for normalisation. This page describes
 
   6 how tokenizers are expected to work and the public API that needs to be
 
   7 implemented when creating a new tokenizer. For information on how to configure
 
   8 a specific tokenizer for a database see the
 
   9 [tokenizer chapter in the Customization Guide](../customize/Tokenizers.md).
 
  11 ## Generic Architecture
 
  13 ### About Search Tokens
 
  15 Search in Nominatim is organised around search tokens. Such a token represents
 
  16 string that can be part of the search query. Tokens are used so that the search
 
  17 index does not need to be organised around strings. Instead the database saves
 
  18 for each place which tokens match this place's name, address, house number etc.
 
  19 To be able to distinguish between these different types of information stored
 
  20 with the place, a search token also always has a certain type: name, house number,
 
  23 During search an incoming query is transformed into a ordered list of such
 
  24 search tokens (or rather many lists, see below) and this list is then converted
 
  25 into a database query to find the right place.
 
  27 It is the core task of the tokenizer to create, manage and assign the search
 
  28 tokens. The tokenizer is involved in two distinct operations:
 
  30 * __at import time__: scanning names of OSM objects, normalizing them and
 
  31   building up the list of search tokens.
 
  32 * __at query time__: scanning the query and returning the appropriate search
 
  38 The indexer is responsible to enrich an OSM object (or place) with all data
 
  39 required for geocoding. It is split into two parts: the controller collects
 
  40 the places that require updating, enriches the place information as required
 
  41 and hands the place to Postgresql. The collector is part of the Nominatim
 
  42 library written in Python. Within Postgresql, the `placex_update`
 
  43 trigger is responsible to fill out all secondary tables with extra geocoding
 
  44 information. This part is written in PL/pgSQL.
 
  46 The tokenizer is involved in both parts. When the indexer prepares a place,
 
  47 it hands it over to the tokenizer to inspect the names and create all the
 
  48 search tokens applicable for the place. This usually involves updating the
 
  49 tokenizer's internal token lists and creating a list of all token IDs for
 
  50 the specific place. This list is later needed in the PL/pgSQL part where the
 
  51 indexer needs to add the token IDs to the appropriate search tables. To be
 
  52 able to communicate the list between the Python part and the pl/pgSQL trigger,
 
  53 the `placex` table contains a special JSONB column `token_info` which is there
 
  54 for the exclusive use of the tokenizer.
 
  56 The Python part of the tokenizer returns a structured information about the
 
  57 tokens of a place to the indexer which converts it to JSON and inserts it into
 
  58 the `token_info` column. The content of the column is then handed to the PL/pqSQL
 
  59 callbacks of the tokenizer which extracts the required information. Usually
 
  60 the tokenizer then removes all information from the `token_info` structure,
 
  61 so that no information is ever persistently saved in the table. All information
 
  62 that went in should have been processed after all and put into secondary tables.
 
  63 This is however not a hard requirement. If the tokenizer needs to store
 
  64 additional information about a place permanently, it may do so in the
 
  65 `token_info` column. It just may never execute searches over it and
 
  66 consequently not create any special indexes on it.
 
  70 At query time, Nominatim builds up multiple _interpretations_ of the search
 
  71 query. Each of these interpretations is tried against the database in order
 
  72 of the likelihood with which they match to the search query. The first
 
  73 interpretation that yields results wins.
 
  75 The interpretations are encapsulated in the `SearchDescription` class. An
 
  76 instance of this class is created by applying a sequence of
 
  77 _search tokens_ to an initially empty SearchDescription. It is the
 
  78 responsibility of the tokenizer to parse the search query and derive all
 
  79 possible sequences of search tokens. To that end the tokenizer needs to parse
 
  80 the search query and look up matching words in its own data structures.
 
  84 The following section describes the functions that need to be implemented
 
  85 for a custom tokenizer implementation.
 
  88     This API is currently in early alpha status. While this API is meant to
 
  89     be a public API on which other tokenizers may be implemented, the API is
 
  90     far away from being stable at the moment.
 
  92 ### Directory Structure
 
  94 Nominatim expects two files containing the Python part of the implementation:
 
  96  * `src/nominatim_db/tokenizer/<NAME>_tokenizer.py` contains the tokenizer
 
  97    code used during import and
 
  98  * `src/nominatim_api/search/<NAME>_tokenizer.py` has the code used during
 
 101 `<NAME>` is a unique name for the tokenizer consisting of only lower-case
 
 102 letters, digits and underscore. A tokenizer also needs to install some SQL
 
 103 functions. By convention, these should be placed in `lib-sql/tokenizer`.
 
 105 If the tokenizer has a default configuration file, this should be saved in
 
 106 `settings/<NAME>_tokenizer.<SUFFIX>`.
 
 108 ### Configuration and Persistence
 
 110 Tokenizers may define custom settings for their configuration. All settings
 
 111 must be prefixed with `NOMINATIM_TOKENIZER_`. Settings may be transient or
 
 112 persistent. Transient settings are loaded from the configuration file when
 
 113 Nominatim is started and may thus be changed at any time. Persistent settings
 
 114 are tied to a database installation and must only be read during installation
 
 115 time. If they are needed for the runtime then they must be saved into the
 
 116 `nominatim_properties` table and later loaded from there.
 
 118 ### The Python modules
 
 120 #### `src/nominatim_db/tokenizer/`
 
 122 The import Python module is expected to export a single factory function:
 
 125 def create(dsn: str, data_dir: Path) -> AbstractTokenizer
 
 128 The `dsn` parameter contains the DSN of the Nominatim database. The `data_dir`
 
 129 is a directory in the project directory that the tokenizer may use to save
 
 130 database-specific data. The function must return the instance of the tokenizer
 
 131 class as defined below.
 
 133 #### `src/nominatim_api/search/`
 
 135 The query-time Python module must also export a factory function:
 
 138 def create_query_analyzer(conn: SearchConnection) -> AbstractQueryAnalyzer
 
 141 The `conn` parameter contains the current search connection. See the
 
 142 [library documentation](../library/Low-Level-DB-Access.md#searchconnection-class)
 
 143 for details on the class. The function must return the instance of the tokenizer
 
 144 class as defined below.
 
 147 ### Python Tokenizer Class
 
 149 All tokenizers must inherit from `nominatim_db.tokenizer.base.AbstractTokenizer`
 
 150 and implement the abstract functions defined there.
 
 152 ::: nominatim_db.tokenizer.base.AbstractTokenizer
 
 156 ### Python Analyzer Class
 
 158 ::: nominatim_db.tokenizer.base.AbstractAnalyzer
 
 163 ### Python Query Analyzer Class
 
 165 ::: nominatim_api.search.query_analyzer_factory.AbstractQueryAnalyzer
 
 169 ### PL/pgSQL Functions
 
 171 The tokenizer must provide access functions for the `token_info` column
 
 172 to the indexer which extracts the necessary information for the global
 
 173 search tables. If the tokenizer needs additional SQL functions for private
 
 174 use, then these functions must be prefixed with `token_` in order to ensure
 
 175 that there are no naming conflicts with the SQL indexer code.
 
 177 The following functions are expected:
 
 180 FUNCTION token_get_name_search_tokens(info JSONB) RETURNS INTEGER[]
 
 183 Return an array of token IDs of search terms that should match
 
 184 the name(s) for the given place. These tokens are used to look up the place
 
 185 by name and, where the place functions as part of an address for another place,
 
 186 by address. Must return NULL when the place has no name.
 
 189 FUNCTION token_get_name_match_tokens(info JSONB) RETURNS INTEGER[]
 
 192 Return an array of token IDs of full names of the place that should be used
 
 193 to match addresses. The list of match tokens is usually more strict than
 
 194 search tokens as it is used to find a match between two OSM tag values which
 
 195 are expected to contain matching full names. Partial terms should not be
 
 196 used for match tokens. Must return NULL when the place has no name.
 
 199 FUNCTION token_get_housenumber_search_tokens(info JSONB) RETURNS INTEGER[]
 
 202 Return an array of token IDs of house number tokens that apply to the place.
 
 203 Note that a place may have multiple house numbers, for example when apartments
 
 204 each have their own number. Must be NULL when the place has no house numbers.
 
 207 FUNCTION token_normalized_housenumber(info JSONB) RETURNS TEXT
 
 210 Return the house number(s) in the normalized form that can be matched against
 
 211 a house number token text. If a place has multiple house numbers they must
 
 212 be listed with a semicolon as delimiter. Must be NULL when the place has no
 
 216 FUNCTION token_is_street_address(info JSONB) RETURNS BOOLEAN
 
 219 Return true if this is an object that should be parented against a street.
 
 220 Only relevant for objects with address rank 30.
 
 223 FUNCTION token_has_addr_street(info JSONB) RETURNS BOOLEAN
 
 226 Return true if there are street names to match against for finding the
 
 227 parent of the object.
 
 231 FUNCTION token_has_addr_place(info JSONB) RETURNS BOOLEAN
 
 234 Return true if there are place names to match against for finding the
 
 235 parent of the object.
 
 238 FUNCTION token_matches_street(info JSONB, street_tokens INTEGER[]) RETURNS BOOLEAN
 
 241 Check if the given tokens (previously saved from `token_get_name_match_tokens()`)
 
 242 match against the `addr:street` tag name. Must return either NULL or FALSE
 
 243 when the place has no `addr:street` tag.
 
 246 FUNCTION token_matches_place(info JSONB, place_tokens INTEGER[]) RETURNS BOOLEAN
 
 249 Check if the given tokens (previously saved from `token_get_name_match_tokens()`)
 
 250 match against the `addr:place` tag name. Must return either NULL or FALSE
 
 251 when the place has no `addr:place` tag.
 
 255 FUNCTION token_addr_place_search_tokens(info JSONB) RETURNS INTEGER[]
 
 258 Return the search token IDs extracted from the `addr:place` tag. These tokens
 
 259 are used for searches by address when no matching place can be found in the
 
 260 database. Must be NULL when the place has no `addr:place` tag.
 
 263 FUNCTION token_get_address_keys(info JSONB) RETURNS SETOF TEXT
 
 266 Return the set of keys for which address information is provided. This
 
 267 should correspond to the list of (relevant) `addr:*` tags with the `addr:`
 
 268 prefix removed or the keys used in the `address` dictionary of the place info.
 
 271 FUNCTION token_get_address_search_tokens(info JSONB, key TEXT) RETURNS INTEGER[]
 
 274 Return the array of search tokens for the given address part. `key` can be
 
 275 expected to be one of those returned with `token_get_address_keys()`. The
 
 276 search tokens are added to the address search vector of the place, when no
 
 277 corresponding OSM object could be found for the given address part from which
 
 278 to copy the name information.
 
 281 FUNCTION token_matches_address(info JSONB, key TEXT, tokens INTEGER[])
 
 284 Check if the given tokens match against the address part `key`.
 
 286 __Warning:__ the tokens that are handed in are the lists previously saved
 
 287 from `token_get_name_search_tokens()`, _not_ from the match token list. This
 
 288 is an historical oddity which will be fixed at some point in the future.
 
 289 Currently, tokenizers are encouraged to make sure that matching works against
 
 290 both the search token list and the match token list.
 
 293 FUNCTION token_get_postcode(info JSONB) RETURNS TEXT
 
 296 Return the postcode for the object, if any exists. The postcode must be in
 
 297 the form that should also be presented to the end-user.
 
 300 FUNCTION token_strip_info(info JSONB) RETURNS JSONB
 
 303 Return the part of the `token_info` field that should be stored in the database
 
 304 permanently. The indexer calls this function when all processing is done and
 
 305 replaces the content of the `token_info` column with the returned value before
 
 306 the trigger stores the information in the database. May return NULL if no
 
 307 information should be stored permanently.