2 Collection of functions that check if the database is complete and functional.
5 from textwrap import dedent
9 from nominatim.db.connection import connect
10 from nominatim.errors import UsageError
14 class CheckState(Enum):
15 """ Possible states of a check. FATAL stops check execution entirely.
22 def _check(hint=None):
23 """ Decorator for checks. It adds the function to the list of
24 checks to execute and adds the code for printing progress messages.
27 title = func.__doc__.split('\n', 1)[0].strip()
28 def run_check(conn, config):
29 print(title, end=' ... ')
30 ret = func(conn, config)
31 if isinstance(ret, tuple):
35 if ret == CheckState.OK:
36 print('\033[92mOK\033[0m')
37 elif ret == CheckState.NOT_APPLICABLE:
38 print('not applicable')
40 print('\x1B[31mFailed\033[0m')
42 print(dedent(hint.format(**params)))
45 CHECKLIST.append(run_check)
50 class _BadConnection: # pylint: disable=R0903
52 def __init__(self, msg):
56 """ Dummy function to provide the implementation.
59 def check_database(config):
60 """ Run a number of checks on the database and return the status.
63 conn = connect(config.get_libpq_dsn()).connection
64 except UsageError as err:
65 conn = _BadConnection(str(err))
68 for check in CHECKLIST:
69 ret = check(conn, config)
70 if ret == CheckState.FATAL:
73 if ret in (CheckState.FATAL, CheckState.FAIL):
80 def _get_indexes(conn):
81 indexes = ['idx_word_word_id',
82 'idx_place_addressline_address_place_id',
83 'idx_placex_rank_search',
84 'idx_placex_rank_address',
85 'idx_placex_parent_place_id',
86 'idx_placex_geometry_reverse_lookuppolygon',
87 'idx_placex_geometry_placenode',
88 'idx_osmline_parent_place_id',
89 'idx_osmline_parent_osm_id',
91 'idx_postcode_postcode'
93 if conn.table_exists('search_name'):
94 indexes.extend(('idx_search_name_nameaddress_vector',
95 'idx_search_name_name_vector',
96 'idx_search_name_centroid'))
97 if conn.table_exists('place'):
98 indexes.extend(('idx_placex_pendingsector',
99 'idx_location_area_country_place_id',
100 'idx_place_osm_unique'
102 if conn.server_version_tuple() >= (11, 0, 0):
103 indexes.extend(('idx_placex_housenumber',
104 'idx_osmline_parent_osm_id_with_hnr'))
111 # Functions are exectured in the order they appear here.
117 * Is the database server started?
118 * Check the NOMINATIM_DATABASE_DSN variable in your local .env
119 * Try connecting to the database with the same settings
121 Project directory: {config.project_dir}
122 Current setting of NOMINATIM_DATABASE_DSN: {config.DATABASE_DSN}
124 def check_connection(conn, config):
125 """ Checking database connection
127 if isinstance(conn, _BadConnection):
128 return CheckState.FATAL, dict(error=conn.msg, config=config)
133 placex table not found
136 * Are you connecting to the right database?
137 * Did the import process finish without errors?
139 Project directory: {config.project_dir}
140 Current setting of NOMINATIM_DATABASE_DSN: {config.DATABASE_DSN}
142 def check_placex_table(conn, config):
143 """ Checking for placex table
145 if conn.table_exists('placex'):
148 return CheckState.FATAL, dict(config=config)
151 @_check(hint="""placex table has no data. Did the import finish sucessfully?""")
152 def check_placex_size(conn, config): # pylint: disable=W0613
153 """ Checking for placex content
155 with conn.cursor() as cur:
156 cnt = cur.scalar('SELECT count(*) FROM (SELECT * FROM placex LIMIT 100) x')
158 return CheckState.OK if cnt > 0 else CheckState.FATAL
162 The Postgresql extension nominatim.so was not correctly loaded.
167 * Check the output of the CMmake/make installation step
168 * Does nominatim.so exist?
169 * Does nominatim.so exist on the database server?
170 * Can nominatim.so be accessed by the database user?
172 def check_module(conn, config): # pylint: disable=W0613
173 """ Checking that nominatim.so module is installed
175 with conn.cursor() as cur:
177 out = cur.scalar("SELECT make_standard_name('a')")
178 except psycopg2.ProgrammingError as err:
179 return CheckState.FAIL, dict(error=str(err))
182 return CheckState.FAIL, dict(error='Unexpected result for make_standard_name()')
188 The indexing didn't finish. {count} entries are not yet indexed.
190 To index the remaining entries, run: {index_cmd}
192 def check_indexing(conn, config): # pylint: disable=W0613
193 """ Checking indexing status
195 with conn.cursor() as cur:
196 cnt = cur.scalar('SELECT count(*) FROM placex WHERE indexed_status > 0')
201 if conn.index_exists('idx_word_word_id'):
202 # Likely just an interrupted update.
203 index_cmd = 'nominatim index'
205 # Looks like the import process got interrupted.
206 index_cmd = 'nominatim import --continue indexing'
208 return CheckState.FAIL, dict(count=cnt, index_cmd=index_cmd)
212 The following indexes are missing:
215 Rerun the index creation with: nominatim import --continue db-postprocess
217 def check_database_indexes(conn, config): # pylint: disable=W0613
218 """ Checking that database indexes are complete
221 for index in _get_indexes(conn):
222 if not conn.index_exists(index):
223 missing.append(index)
226 return CheckState.FAIL, dict(indexes='\n '.join(missing))
232 At least one index is invalid. That can happen, e.g. when index creation was
233 disrupted and later restarted. You should delete the affected indices
239 def check_database_index_valid(conn, config): # pylint: disable=W0613
240 """ Checking that all database indexes are valid
242 with conn.cursor() as cur:
243 cur.execute(""" SELECT relname FROM pg_class, pg_index
244 WHERE pg_index.indisvalid = false
245 AND pg_index.indexrelid = pg_class.oid""")
250 return CheckState.FAIL, dict(indexes='\n '.join(broken))
257 Run TIGER import again: nominatim add-data --tiger-data <DIR>
259 def check_tiger_table(conn, config):
260 """ Checking TIGER external data table.
262 if not config.get_bool('USE_US_TIGER_DATA'):
263 return CheckState.NOT_APPLICABLE
265 if not conn.table_exists('location_property_tiger'):
266 return CheckState.FAIL, dict(error='TIGER data table not found.')
268 with conn.cursor() as cur:
269 if cur.scalar('SELECT count(*) FROM location_property_tiger') == 0:
270 return CheckState.FAIL, dict(error='TIGER data table is empty.')