]> git.openstreetmap.org Git - nominatim.git/blob - nominatim/tools/database_import.py
move import-data option to native python
[nominatim.git] / nominatim / tools / database_import.py
1 """
2 Functions for setting up and importing a new Nominatim database.
3 """
4 import logging
5 import os
6 import subprocess
7 import shutil
8 from pathlib import Path
9
10 import psutil
11
12 from ..db.connection import connect, get_pg_env
13 from ..db import utils as db_utils
14 from .exec_utils import run_osm2pgsql
15 from ..errors import UsageError
16 from ..version import POSTGRESQL_REQUIRED_VERSION, POSTGIS_REQUIRED_VERSION
17
18 LOG = logging.getLogger()
19
20 def create_db(dsn, rouser=None):
21     """ Create a new database for the given DSN. Fails when the database
22         already exists or the PostgreSQL version is too old.
23         Uses `createdb` to create the database.
24
25         If 'rouser' is given, then the function also checks that the user
26         with that given name exists.
27
28         Requires superuser rights by the caller.
29     """
30     proc = subprocess.run(['createdb'], env=get_pg_env(dsn), check=False)
31
32     if proc.returncode != 0:
33         raise UsageError('Creating new database failed.')
34
35     with connect(dsn) as conn:
36         postgres_version = conn.server_version_tuple()
37         if postgres_version < POSTGRESQL_REQUIRED_VERSION:
38             LOG.fatal('Minimum supported version of Postgresql is %d.%d. '
39                       'Found version %d.%d.',
40                       POSTGRESQL_REQUIRED_VERSION[0], POSTGRESQL_REQUIRED_VERSION[1],
41                       postgres_version[0], postgres_version[1])
42             raise UsageError('PostgreSQL server is too old.')
43
44         if rouser is not None:
45             with conn.cursor() as cur:
46                 cnt = cur.scalar('SELECT count(*) FROM pg_user where usename = %s',
47                                  (rouser, ))
48                 if cnt == 0:
49                     LOG.fatal("Web user '%s' does not exists. Create it with:\n"
50                               "\n      createuser %s", rouser, rouser)
51                     raise UsageError('Missing read-only user.')
52
53
54
55 def setup_extensions(conn):
56     """ Set up all extensions needed for Nominatim. Also checks that the
57         versions of the extensions are sufficient.
58     """
59     with conn.cursor() as cur:
60         cur.execute('CREATE EXTENSION IF NOT EXISTS hstore')
61         cur.execute('CREATE EXTENSION IF NOT EXISTS postgis')
62     conn.commit()
63
64     postgis_version = conn.postgis_version_tuple()
65     if postgis_version < POSTGIS_REQUIRED_VERSION:
66         LOG.fatal('Minimum supported version of PostGIS is %d.%d. '
67                   'Found version %d.%d.',
68                   POSTGIS_REQUIRED_VERSION[0], POSTGIS_REQUIRED_VERSION[1],
69                   postgis_version[0], postgis_version[1])
70         raise UsageError('PostGIS version is too old.')
71
72
73 def install_module(src_dir, project_dir, module_dir):
74     """ Copy the normalization module from src_dir into the project
75         directory under the '/module' directory. If 'module_dir' is set, then
76         use the module from there instead and check that it is accessible
77         for Postgresql.
78
79         The function detects when the installation is run from the
80         build directory. It doesn't touch the module in that case.
81     """
82     if not module_dir:
83         module_dir = project_dir / 'module'
84
85         if not module_dir.exists() or not src_dir.samefile(module_dir):
86
87             if not module_dir.exists():
88                 module_dir.mkdir()
89
90             destfile = module_dir / 'nominatim.so'
91             shutil.copy(str(src_dir / 'nominatim.so'), str(destfile))
92             destfile.chmod(0o755)
93
94             LOG.info('Database module installed at %s', str(destfile))
95         else:
96             LOG.info('Running from build directory. Leaving database module as is.')
97     else:
98         LOG.info("Using custom path for database module at '%s'", module_dir)
99
100     return module_dir
101
102
103 def check_module_dir_path(conn, path):
104     """ Check that the normalisation module can be found and executed
105         from the given path.
106     """
107     with conn.cursor() as cur:
108         cur.execute("""CREATE FUNCTION nominatim_test_import_func(text)
109                        RETURNS text AS '{}/nominatim.so', 'transliteration'
110                        LANGUAGE c IMMUTABLE STRICT;
111                        DROP FUNCTION nominatim_test_import_func(text)
112                     """.format(path))
113
114
115 def import_base_data(dsn, sql_dir, ignore_partitions=False):
116     """ Create and populate the tables with basic static data that provides
117         the background for geocoding. Data is assumed to not yet exist.
118     """
119     db_utils.execute_file(dsn, sql_dir / 'country_name.sql')
120     db_utils.execute_file(dsn, sql_dir / 'country_osm_grid.sql.gz')
121
122     if ignore_partitions:
123         with connect(dsn) as conn:
124             with conn.cursor() as cur:
125                 cur.execute('UPDATE country_name SET partition = 0')
126             conn.commit()
127
128
129 def import_osm_data(osm_file, options, drop=False):
130     """ Import the given OSM file. 'options' contains the list of
131         default settings for osm2pgsql.
132     """
133     options['import_file'] = osm_file
134     options['append'] = False
135     options['threads'] = 1
136
137     if not options['flatnode_file'] and options['osm2pgsql_cache'] == 0:
138         # Make some educated guesses about cache size based on the size
139         # of the import file and the available memory.
140         mem = psutil.virtual_memory()
141         fsize = os.stat(str(osm_file)).st_size
142         options['osm2pgsql_cache'] = int(min((mem.available + mem.cached) * 0.75,
143                                              fsize * 2) / 1024 / 1024) + 1
144
145     run_osm2pgsql(options)
146
147     with connect(options['dsn']) as conn:
148         with conn.cursor() as cur:
149             cur.execute('SELECT * FROM place LIMIT 1')
150             if cur.rowcount == 0:
151                 raise UsageError('No data imported by osm2pgsql.')
152
153         if drop:
154             conn.drop_table('planet_osm_nodes')
155
156     if drop:
157         if options['flatnode_file']:
158             Path(options['flatnode_file']).unlink()