]> git.openstreetmap.org Git - nominatim.git/blob - src/nominatim_db/tools/admin.py
prepare release 5.3.2-post1
[nominatim.git] / src / nominatim_db / tools / admin.py
1 # SPDX-License-Identifier: GPL-3.0-or-later
2 #
3 # This file is part of Nominatim. (https://nominatim.org)
4 #
5 # Copyright (C) 2024 by the Nominatim developer community.
6 # For a full list of authors see the git log.
7 """
8 Functions for database analysis and maintenance.
9 """
10 from typing import Optional, Tuple, Any, cast
11 import logging
12
13 import psycopg
14 from psycopg.types.json import Json
15
16 from ..typing import DictCursorResult
17 from ..config import Configuration
18 from ..db.connection import connect, Cursor, register_hstore
19 from ..db.sql_preprocessor import SQLPreprocessor
20 from ..errors import UsageError
21 from ..tokenizer import factory as tokenizer_factory
22 from ..data.place_info import PlaceInfo
23
24 LOG = logging.getLogger()
25
26
27 def _get_place_info(cursor: Cursor, osm_id: Optional[str],
28                     place_id: Optional[int]) -> DictCursorResult:
29     sql = """SELECT place_id, extra.*
30              FROM placex, LATERAL placex_indexing_prepare(placex) as extra
31           """
32
33     values: Tuple[Any, ...]
34     if osm_id:
35         osm_type = osm_id[0].upper()
36         if osm_type not in 'NWR' or not osm_id[1:].isdigit():
37             LOG.fatal('OSM ID must be of form <N|W|R><id>. Got: %s', osm_id)
38             raise UsageError("OSM ID parameter badly formatted")
39
40         sql += ' WHERE placex.osm_type = %s AND placex.osm_id = %s'
41         values = (osm_type, int(osm_id[1:]))
42     elif place_id is not None:
43         sql += ' WHERE placex.place_id = %s'
44         values = (place_id, )
45     else:
46         LOG.fatal("No OSM object given to index.")
47         raise UsageError("OSM object not found")
48
49     cursor.execute(sql + ' LIMIT 1', values)
50
51     if cursor.rowcount < 1:
52         LOG.fatal("OSM object %s not found in database.", osm_id)
53         raise UsageError("OSM object not found")
54
55     return cast(DictCursorResult, cursor.fetchone())
56
57
58 def analyse_indexing(config: Configuration, osm_id: Optional[str] = None,
59                      place_id: Optional[int] = None) -> None:
60     """ Analyse indexing of a single Nominatim object.
61     """
62     with connect(config.get_libpq_dsn()) as conn:
63         register_hstore(conn)
64         with conn.cursor(row_factory=psycopg.rows.dict_row) as cur:
65             place = _get_place_info(cur, osm_id, place_id)
66
67             cur.execute("update placex set indexed_status = 2 where place_id = %s",
68                         (place['place_id'], ))
69
70             cur.execute("""SET auto_explain.log_min_duration = '0';
71                            SET auto_explain.log_analyze = 'true';
72                            SET auto_explain.log_nested_statements = 'true';
73                            LOAD 'auto_explain';
74                            SET client_min_messages = LOG;
75                            SET log_min_messages = FATAL""")
76
77             tokenizer = tokenizer_factory.get_tokenizer_for_db(config)
78
79             # Enable printing of messages.
80             conn.add_notice_handler(lambda diag: print(diag.message_primary))
81
82             with tokenizer.name_analyzer() as analyzer:
83                 cur.execute("""UPDATE placex
84                                SET indexed_status = 0, address = %s, token_info = %s,
85                                name = %s, linked_place_id = %s
86                                WHERE place_id = %s""",
87                             (place['address'],
88                              Json(analyzer.process_place(PlaceInfo(place))),
89                              place['name'], place['linked_place_id'], place['place_id']))
90
91         # we do not want to keep the results
92         conn.rollback()
93
94
95 def clean_deleted_relations(config: Configuration, age: str) -> None:
96     """ Clean deleted relations older than a given age
97     """
98     with connect(config.get_libpq_dsn()) as conn:
99         with conn.cursor() as cur:
100             try:
101                 cur.execute("""SELECT place_force_delete(p.place_id)
102                             FROM import_polygon_delete d, placex p
103                             WHERE p.osm_type = d.osm_type AND p.osm_id = d.osm_id
104                             AND age(p.indexed_date) > %s::interval""",
105                             (age, ))
106             except psycopg.DataError as exc:
107                 raise UsageError('Invalid PostgreSQL time interval format') from exc
108         conn.commit()
109
110
111 def grant_ro_access(dsn: str, config: Configuration) -> None:
112     """ Grant read-only access to the web user for all Nominatim tables.
113         This can be used to grant access to a different user after import.
114     """
115     with connect(dsn) as conn:
116         sql = SQLPreprocessor(conn, config)
117         sql.run_sql_file(conn, 'grants.sql')