]> git.openstreetmap.org Git - nominatim.git/blob - src/nominatim_db/tools/migration.py
12abe7fea5d3365ba28d4eb9380c6e0585825bda
[nominatim.git] / src / nominatim_db / tools / migration.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 migration to newer software versions.
9 """
10 from typing import List, Tuple, Callable, Any
11 import logging
12
13 from ..errors import UsageError
14 from ..config import Configuration
15 from ..db import properties
16 from ..db.connection import connect, Connection,\
17                             table_exists, register_hstore
18 from ..version import NominatimVersion, NOMINATIM_VERSION, parse_version
19 from ..tokenizer import factory as tokenizer_factory
20 from . import refresh
21
22 LOG = logging.getLogger()
23
24 _MIGRATION_FUNCTIONS : List[Tuple[NominatimVersion, Callable[..., None]]] = []
25
26 def migrate(config: Configuration, paths: Any) -> int:
27     """ Check for the current database version and execute migrations,
28         if necesssary.
29     """
30     with connect(config.get_libpq_dsn()) as conn:
31         register_hstore(conn)
32         if table_exists(conn, 'nominatim_properties'):
33             db_version_str = properties.get_property(conn, 'database_version')
34         else:
35             db_version_str = None
36
37         if db_version_str is not None:
38             db_version = parse_version(db_version_str)
39         else:
40             db_version = None
41
42         if db_version is None or db_version < (4, 3, 0, 0):
43             LOG.fatal('Your database version is older than 4.3. '
44                       'Direct migration is not possible.\n'
45                       'You should strongly consider a reimport. If that is not possible\n'
46                       'please upgrade to 4.3 first and then to the newest version.')
47             raise UsageError('Migration not possible.')
48
49         if db_version == NOMINATIM_VERSION:
50             LOG.warning("Database already at latest version (%s)", db_version_str)
51             return 0
52
53         LOG.info("Detected database version: %s", db_version_str)
54
55         for version, func in _MIGRATION_FUNCTIONS:
56             if db_version < version:
57                 title = func.__doc__ or ''
58                 LOG.warning("Running: %s (%s)", title.split('\n', 1)[0], version)
59                 kwargs = dict(conn=conn, config=config, paths=paths)
60                 func(**kwargs)
61                 conn.commit()
62
63         LOG.warning('Updating SQL functions.')
64         refresh.create_functions(conn, config)
65         tokenizer = tokenizer_factory.get_tokenizer_for_db(config)
66         tokenizer.update_sql_functions(config)
67
68         properties.set_property(conn, 'database_version', str(NOMINATIM_VERSION))
69
70         conn.commit()
71
72     return 0
73
74
75 def _migration(major: int, minor: int, patch: int = 0,
76                dbpatch: int = 0) -> Callable[[Callable[..., None]], Callable[..., None]]:
77     """ Decorator for a single migration step. The parameters describe the
78         version after which the migration is applicable, i.e before changing
79         from the given version to the next, the migration is required.
80
81         All migrations are run in the order in which they are defined in this
82         file. Do not run global SQL scripts for migrations as you cannot be sure
83         that these scripts do the same in later versions.
84
85         Functions will always be reimported in full at the end of the migration
86         process, so the migration functions may leave a temporary state behind
87         there.
88     """
89     def decorator(func: Callable[..., None]) -> Callable[..., None]:
90         version = NominatimVersion(major, minor, patch, dbpatch)
91         _MIGRATION_FUNCTIONS.append((version, func))
92         return func
93
94     return decorator
95
96
97 @_migration(4, 4, 99, 0)
98 def create_postcode_area_lookup_index(conn: Connection, **_: Any) -> None:
99     """ Create index needed for looking up postcode areas from postocde points.
100     """
101     with conn.cursor() as cur:
102         cur.execute("""CREATE INDEX IF NOT EXISTS idx_placex_postcode_areas
103                        ON placex USING BTREE (country_code, postcode)
104                        WHERE osm_type = 'R' AND class = 'boundary' AND type = 'postal_code'
105                     """)
106
107
108 @_migration(4, 4, 99, 1)
109 def create_postcode_parent_index(conn: Connection, **_: Any) -> None:
110     """ Create index needed for updating postcodes when a parent changes.
111     """
112     if table_exists(conn, 'planet_osm_ways'):
113         with conn.cursor() as cur:
114             cur.execute("""CREATE INDEX IF NOT EXISTS
115                              idx_location_postcode_parent_place_id
116                              ON location_postcode USING BTREE (parent_place_id)""")