1 # SPDX-License-Identifier: GPL-2.0-only
3 # This file is part of Nominatim. (https://nominatim.org)
5 # Copyright (C) 2022 by the Nominatim developer community.
6 # For a full list of authors see the git log.
8 Collection of functions that check if the database is complete and functional.
11 from textwrap import dedent
13 from nominatim.db.connection import connect
14 from nominatim.errors import UsageError
15 from nominatim.tokenizer import factory as tokenizer_factory
19 class CheckState(Enum):
20 """ Possible states of a check. FATAL stops check execution entirely.
28 def _check(hint=None):
29 """ Decorator for checks. It adds the function to the list of
30 checks to execute and adds the code for printing progress messages.
33 title = func.__doc__.split('\n', 1)[0].strip()
35 def run_check(conn, config):
36 print(title, end=' ... ')
37 ret = func(conn, config)
38 if isinstance(ret, tuple):
42 if ret == CheckState.OK:
43 print('\033[92mOK\033[0m')
44 elif ret == CheckState.WARN:
45 print('\033[93mWARNING\033[0m')
48 print(dedent(hint.format(**params)))
49 elif ret == CheckState.NOT_APPLICABLE:
50 print('not applicable')
52 print('\x1B[31mFailed\033[0m')
54 print(dedent(hint.format(**params)))
57 CHECKLIST.append(run_check)
64 def __init__(self, msg):
68 """ Dummy function to provide the implementation.
71 def check_database(config):
72 """ Run a number of checks on the database and return the status.
75 conn = connect(config.get_libpq_dsn()).connection
76 except UsageError as err:
77 conn = _BadConnection(str(err))
80 for check in CHECKLIST:
81 ret = check(conn, config)
82 if ret == CheckState.FATAL:
85 if ret in (CheckState.FATAL, CheckState.FAIL):
92 def _get_indexes(conn):
93 indexes = ['idx_place_addressline_address_place_id',
94 'idx_placex_rank_search',
95 'idx_placex_rank_address',
96 'idx_placex_parent_place_id',
97 'idx_placex_geometry_reverse_lookuppolygon',
98 'idx_placex_geometry_placenode',
99 'idx_osmline_parent_place_id',
100 'idx_osmline_parent_osm_id',
102 'idx_postcode_postcode'
104 if conn.table_exists('search_name'):
105 indexes.extend(('idx_search_name_nameaddress_vector',
106 'idx_search_name_name_vector',
107 'idx_search_name_centroid'))
108 if conn.server_version_tuple() >= (11, 0, 0):
109 indexes.extend(('idx_placex_housenumber',
110 'idx_osmline_parent_osm_id_with_hnr'))
111 if conn.table_exists('place'):
112 indexes.extend(('idx_placex_pendingsector',
113 'idx_location_area_country_place_id',
114 'idx_place_osm_unique'))
121 # Functions are exectured in the order they appear here.
127 * Is the database server started?
128 * Check the NOMINATIM_DATABASE_DSN variable in your local .env
129 * Try connecting to the database with the same settings
131 Project directory: {config.project_dir}
132 Current setting of NOMINATIM_DATABASE_DSN: {config.DATABASE_DSN}
134 def check_connection(conn, config):
135 """ Checking database connection
137 if isinstance(conn, _BadConnection):
138 return CheckState.FATAL, dict(error=conn.msg, config=config)
143 placex table not found
146 * Are you connecting to the right database?
147 * Did the import process finish without errors?
149 Project directory: {config.project_dir}
150 Current setting of NOMINATIM_DATABASE_DSN: {config.DATABASE_DSN}
152 def check_placex_table(conn, config):
153 """ Checking for placex table
155 if conn.table_exists('placex'):
158 return CheckState.FATAL, dict(config=config)
161 @_check(hint="""placex table has no data. Did the import finish sucessfully?""")
162 def check_placex_size(conn, _):
163 """ Checking for placex content
165 with conn.cursor() as cur:
166 cnt = cur.scalar('SELECT count(*) FROM (SELECT * FROM placex LIMIT 100) x')
168 return CheckState.OK if cnt > 0 else CheckState.FATAL
171 @_check(hint="""{msg}""")
172 def check_tokenizer(_, config):
173 """ Checking that tokenizer works
176 tokenizer = tokenizer_factory.get_tokenizer_for_db(config)
178 return CheckState.FAIL, dict(msg="""\
179 Cannot load tokenizer. Did the import finish sucessfully?""")
181 result = tokenizer.check_database(config)
186 return CheckState.FAIL, dict(msg=result)
190 Wikipedia/Wikidata importance tables missing.
191 Quality of search results may be degraded. Reverse geocoding is unaffected.
192 See https://nominatim.org/release-docs/latest/admin/Import/#wikipediawikidata-rankings
194 def check_existance_wikipedia(conn, _):
195 """ Checking for wikipedia/wikidata data
197 with conn.cursor() as cur:
198 cnt = cur.scalar('SELECT count(*) FROM wikipedia_article')
200 return CheckState.WARN if cnt == 0 else CheckState.OK
204 The indexing didn't finish. {count} entries are not yet indexed.
206 To index the remaining entries, run: {index_cmd}
208 def check_indexing(conn, _):
209 """ Checking indexing status
211 with conn.cursor() as cur:
212 cnt = cur.scalar('SELECT count(*) FROM placex WHERE indexed_status > 0')
217 if conn.index_exists('idx_placex_rank_search'):
218 # Likely just an interrupted update.
219 index_cmd = 'nominatim index'
221 # Looks like the import process got interrupted.
222 index_cmd = 'nominatim import --continue indexing'
224 return CheckState.FAIL, dict(count=cnt, index_cmd=index_cmd)
228 The following indexes are missing:
231 Rerun the index creation with: nominatim import --continue db-postprocess
233 def check_database_indexes(conn, _):
234 """ Checking that database indexes are complete
237 for index in _get_indexes(conn):
238 if not conn.index_exists(index):
239 missing.append(index)
242 return CheckState.FAIL, dict(indexes='\n '.join(missing))
248 At least one index is invalid. That can happen, e.g. when index creation was
249 disrupted and later restarted. You should delete the affected indices
255 def check_database_index_valid(conn, _):
256 """ Checking that all database indexes are valid
258 with conn.cursor() as cur:
259 cur.execute(""" SELECT relname FROM pg_class, pg_index
260 WHERE pg_index.indisvalid = false
261 AND pg_index.indexrelid = pg_class.oid""")
266 return CheckState.FAIL, dict(indexes='\n '.join(broken))
273 Run TIGER import again: nominatim add-data --tiger-data <DIR>
275 def check_tiger_table(conn, config):
276 """ Checking TIGER external data table.
278 if not config.get_bool('USE_US_TIGER_DATA'):
279 return CheckState.NOT_APPLICABLE
281 if not conn.table_exists('location_property_tiger'):
282 return CheckState.FAIL, dict(error='TIGER data table not found.')
284 with conn.cursor() as cur:
285 if cur.scalar('SELECT count(*) FROM location_property_tiger') == 0:
286 return CheckState.FAIL, dict(error='TIGER data table is empty.')