2 Collection of functions that check if the database is complete and functional.
 
   5 from textwrap import dedent
 
   9 from ..db.connection import connect
 
  10 from ..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_reverse_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'
 
 108 # Functions are exectured in the order they appear here.
 
 114              * Is the database server started?
 
 115              * Check the NOMINATIM_DATABASE_DSN variable in your local .env
 
 116              * Try connecting to the database with the same settings
 
 118              Project directory: {config.project_dir}
 
 119              Current setting of NOMINATIM_DATABASE_DSN: {config.DATABASE_DSN}
 
 121 def check_connection(conn, config):
 
 122     """ Checking database connection
 
 124     if isinstance(conn, _BadConnection):
 
 125         return CheckState.FATAL, dict(error=conn.msg, config=config)
 
 130              placex table not found
 
 133              * Are you connecting to the right database?
 
 134              * Did the import process finish without errors?
 
 136              Project directory: {config.project_dir}
 
 137              Current setting of NOMINATIM_DATABASE_DSN: {config.DATABASE_DSN}
 
 139 def check_placex_table(conn, config):
 
 140     """ Checking for placex table
 
 142     if conn.table_exists('placex'):
 
 145     return CheckState.FATAL, dict(config=config)
 
 148 @_check(hint="""placex table has no data. Did the import finish sucessfully?""")
 
 149 def check_placex_size(conn, config): # pylint: disable=W0613
 
 150     """ Checking for placex content
 
 152     with conn.cursor() as cur:
 
 153         cnt = cur.scalar('SELECT count(*) FROM (SELECT * FROM placex LIMIT 100) x')
 
 155     return CheckState.OK if cnt > 0 else CheckState.FATAL
 
 159              The Postgresql extension nominatim.so was not correctly loaded.
 
 164              * Check the output of the CMmake/make installation step
 
 165              * Does nominatim.so exist?
 
 166              * Does nominatim.so exist on the database server?
 
 167              * Can nominatim.so be accessed by the database user?
 
 169 def check_module(conn, config): # pylint: disable=W0613
 
 170     """ Checking that nominatim.so module is installed
 
 172     with conn.cursor() as cur:
 
 174             out = cur.scalar("SELECT make_standard_name('a')")
 
 175         except psycopg2.ProgrammingError as err:
 
 176             return CheckState.FAIL, dict(error=str(err))
 
 179             return CheckState.FAIL, dict(error='Unexpected result for make_standard_name()')
 
 185              The indexing didn't finish. {count} entries are not yet indexed.
 
 187              To index the remaining entries, run:   {index_cmd}
 
 189 def check_indexing(conn, config): # pylint: disable=W0613
 
 190     """ Checking indexing status
 
 192     with conn.cursor() as cur:
 
 193         cnt = cur.scalar('SELECT count(*) FROM placex WHERE indexed_status > 0')
 
 198     if conn.index_exists('idx_word_word_id'):
 
 199         # Likely just an interrupted update.
 
 200         index_cmd = 'nominatim index'
 
 202         # Looks like the import process got interrupted.
 
 203         index_cmd = 'nominatim import --continue indexing'
 
 205     return CheckState.FAIL, dict(count=cnt, index_cmd=index_cmd)
 
 209              The following indexes are missing:
 
 212              Rerun the index creation with:   nominatim import --continue db-postprocess
 
 214 def check_database_indexes(conn, config): # pylint: disable=W0613
 
 215     """ Checking that database indexes are complete
 
 218     for index in _get_indexes(conn):
 
 219         if not conn.index_exists(index):
 
 220             missing.append(index)
 
 223         return CheckState.FAIL, dict(indexes='\n  '.join(missing))
 
 229              At least one index is invalid. That can happen, e.g. when index creation was
 
 230              disrupted and later restarted. You should delete the affected indices
 
 236 def check_database_index_valid(conn, config): # pylint: disable=W0613
 
 237     """ Checking that all database indexes are valid
 
 239     with conn.cursor() as cur:
 
 240         cur.execute(""" SELECT relname FROM pg_class, pg_index
 
 241                         WHERE pg_index.indisvalid = false
 
 242                         AND pg_index.indexrelid = pg_class.oid""")
 
 247         return CheckState.FAIL, dict(indexes='\n  '.join(broken))
 
 254              Run TIGER import again:   nominatim add-data --tiger-data <DIR>
 
 256 def check_tiger_table(conn, config):
 
 257     """ Checking TIGER external data table.
 
 259     if not config.get_bool('USE_US_TIGER_DATA'):
 
 260         return CheckState.NOT_APPLICABLE
 
 262     if not conn.table_exists('location_property_tiger'):
 
 263         return CheckState.FAIL, dict(error='TIGER data table not found.')
 
 265     with conn.cursor() as cur:
 
 266         if cur.scalar('SELECT count(*) FROM location_property_tiger') == 0:
 
 267             return CheckState.FAIL, dict(error='TIGER data table is empty.')