]> git.openstreetmap.org Git - nominatim.git/commitdiff
Merge pull request #2192 from lonvia/database-versioning
authorSarah Hoffmann <lonvia@denofr.de>
Tue, 2 Mar 2021 14:57:46 +0000 (15:57 +0100)
committerGitHub <noreply@github.com>
Tue, 2 Mar 2021 14:57:46 +0000 (15:57 +0100)
Introduce database versioning

13 files changed:
docs/api/Status.md
lib-php/Status.php
lib-php/setup/SetupClass.php
lib-php/website/status.php
lib-sql/tables.sql
nominatim/clicmd/setup.py
nominatim/db/properties.py [new file with mode: 0644]
nominatim/tools/exec_utils.py
nominatim/tools/refresh.py
nominatim/version.py
test/python/test_cli.py
test/python/test_db_connection.py
test/python/test_db_properties.py [new file with mode: 0644]

index 0241b6faa35c6bf93cfa6c3fda383b36fff2c055..8c3e25e94339085b7c2a52bd9cc358b7209f863e 100644 (file)
@@ -35,10 +35,16 @@ will return HTTP code 200 and a structure
   {
       "status": 0,
       "message": "OK",
-      "data_updated": "2020-05-04T14:47:00+00:00"
+      "data_updated": "2020-05-04T14:47:00+00:00",
+      "software_version": "3.6.0-0",
+      "database_version": "3.6.0-0"
   }
 ```
 
+The `software_version` field contains the version of Nominatim used to serve
+the API. The `database_version` field contains the version of the data format
+in the database.
+
 On error will also return HTTP status code 200 and a structure with error
 code and message, e.g.
 
index a276c4d5faa2c8ffe6d2bb672a5085c384ddd393..2d9e78db42606f59a90cb2d6e18ad4d8774a6a2e 100644 (file)
@@ -56,4 +56,10 @@ class Status
 
         return $iDataDateEpoch;
     }
+
+    public function databaseVersion()
+    {
+        $sSQL = 'SELECT value FROM nominatim_properties WHERE property = \'database_version\'';
+        return $this->oDB->getOne($sSQL);
+    }
 }
index b0081fd873ce3bce62cbc318c3b6e815db1026dc..7516861932f4cd9aef328a0d6ea12aa59b9035fb 100755 (executable)
@@ -13,7 +13,6 @@ class SetupFunctions
     protected $sIgnoreErrors;
     protected $bEnableDiffUpdates;
     protected $bEnableDebugStatements;
-    protected $bNoPartitions;
     protected $bDrop;
     protected $oDB = null;
     protected $oNominatimCmd;
@@ -51,11 +50,6 @@ class SetupFunctions
         } else {
             $this->bEnableDebugStatements = false;
         }
-        if (isset($aCMDResult['no-partitions'])) {
-            $this->bNoPartitions = $aCMDResult['no-partitions'];
-        } else {
-            $this->bNoPartitions = false;
-        }
         if (isset($aCMDResult['enable-diff-updates'])) {
             $this->bEnableDiffUpdates = $aCMDResult['enable-diff-updates'];
         } else {
@@ -350,9 +344,9 @@ class SetupFunctions
 
     private function pgsqlRunPartitionScript($sTemplate)
     {
-        $sSQL = 'select distinct partition from country_name';
+        $sSQL = 'select distinct partition from country_name order by partition';
         $aPartitions = $this->db()->getCol($sSQL);
-        if (!$this->bNoPartitions) $aPartitions[] = 0;
+        if ($aPartitions[0] != 0) $aPartitions[] = 0;
 
         preg_match_all('#^-- start(.*?)^-- end#ms', $sTemplate, $aMatches, PREG_SET_ORDER);
         foreach ($aMatches as $aMatch) {
index 9f030fb3ea38314047b50973bc6929aa80117de3..7c7eb9281c5e449fb09a52f18ce8c1153273e7e7 100644 (file)
@@ -37,8 +37,13 @@ if ($sOutputFormat == 'json') {
     $aResponse = array(
                   'status' => 0,
                   'message' => 'OK',
-                  'data_updated' => (new DateTime('@'.$epoch))->format(DateTime::RFC3339)
+                  'data_updated' => (new DateTime('@'.$epoch))->format(DateTime::RFC3339),
+                  'software_version' => CONST_NominatimVersion
                  );
+    $sDatabaseVersion = $oStatus->databaseVersion();
+    if ($sDatabaseVersion) {
+        $aResponse['database_version'] = $sDatabaseVersion;
+    }
     javascript_renderData($aResponse);
 } else {
     echo 'OK';
index d15e42c445eebf71d5cbc1d0b005ee4f1ad3a6bc..ce11c4105c4a8463f9ac96570648c7f29a200a35 100644 (file)
@@ -36,6 +36,13 @@ GRANT SELECT ON new_query_log TO "{www-user}" ;
 
 GRANT SELECT ON TABLE country_name TO "{www-user}";
 
+DROP TABLE IF EXISTS nominatim_properties;
+CREATE TABLE nominatim_properties (
+    property TEXT,
+    value TEXT
+);
+GRANT SELECT ON TABLE nominatim_properties TO "{www-user}";
+
 drop table IF EXISTS word;
 CREATE TABLE word (
   word_id INTEGER,
index f8229b391d78ff602afbaaa03864e4b3d9600ed8..ab57d59b1c696322c6320f95559c9dc451d31ade 100644 (file)
@@ -8,7 +8,8 @@ import psutil
 
 from ..tools.exec_utils import run_legacy_script
 from ..db.connection import connect
-from ..db import status
+from ..db import status, properties
+from ..version import NOMINATIM_VERSION
 from ..errors import UsageError
 
 # Do not repeat documentation of subcommand classes.
@@ -87,7 +88,8 @@ class SetupAll:
             params = ['setup.php', '--create-tables', '--create-partition-tables']
             if args.reverse_only:
                 params.append('--reverse-only')
-            run_legacy_script(*params, nominatim_env=args)
+            run_legacy_script(*params, nominatim_env=args,
+                              throw_on_fail=not args.ignore_errors)
 
             LOG.warning('Create functions (2nd pass)')
             with connect(args.config.get_libpq_dsn()) as conn:
@@ -112,7 +114,8 @@ class SetupAll:
                                       args.threads or psutil.cpu_count() or 1)
 
             LOG.warning('Calculate postcodes')
-            run_legacy_script('setup.php', '--calculate-postcodes', nominatim_env=args)
+            run_legacy_script('setup.php', '--calculate-postcodes',
+                              nominatim_env=args, throw_on_fail=not args.ignore_errors)
 
         if args.continue_at is None or args.continue_at in ('load-data', 'indexing'):
             LOG.warning('Indexing places')
@@ -124,7 +127,7 @@ class SetupAll:
         params = ['setup.php', '--create-search-indices', '--create-country-names']
         if args.no_updates:
             params.append('--drop')
-        run_legacy_script(*params, nominatim_env=args)
+        run_legacy_script(*params, nominatim_env=args, throw_on_fail=not args.ignore_errors)
 
         webdir = args.project_dir / 'website'
         LOG.warning('Setup website at %s', webdir)
@@ -138,4 +141,7 @@ class SetupAll:
             except Exception as exc: # pylint: disable=broad-except
                 LOG.error('Cannot determine date of database: %s', exc)
 
+            properties.set_property(conn, 'database_version',
+                                    '{0[0]}.{0[1]}.{0[2]}-{0[3]}'.format(NOMINATIM_VERSION))
+
         return 0
diff --git a/nominatim/db/properties.py b/nominatim/db/properties.py
new file mode 100644 (file)
index 0000000..9cc371f
--- /dev/null
@@ -0,0 +1,28 @@
+"""
+Query and access functions for the in-database property table.
+"""
+
+def set_property(conn, name, value):
+    """ Add or replace the propery with the given name.
+    """
+    with conn.cursor() as cur:
+        cur.execute('SELECT value FROM nominatim_properties WHERE property = %s',
+                    (name, ))
+
+        if cur.rowcount == 0:
+            sql = 'INSERT INTO nominatim_properties (value, property) VALUES (%s, %s)'
+        else:
+            sql = 'UPDATE nominatim_properties SET value = %s WHERE property = %s'
+
+        cur.execute(sql, (value, name))
+    conn.commit()
+
+def get_property(conn, name):
+    """ Return the current value of the given propery or None if the property
+        is not set.
+    """
+    with conn.cursor() as cur:
+        cur.execute('SELECT value FROM nominatim_properties WHERE property = %s',
+                    (name, ))
+
+        return cur.fetchone()[0] if cur.rowcount > 0 else None
index b476c1231d130099bf1ca3ee321bcceaf52d382e..e6b9d8d4d15f032cfc1bdf3312f764546b6960f7 100644 (file)
@@ -134,7 +134,7 @@ def run_osm2pgsql(options):
 def get_url(url):
     """ Get the contents from the given URL and return it as a UTF-8 string.
     """
-    headers = {"User-Agent" : "Nominatim/" + NOMINATIM_VERSION}
+    headers = {"User-Agent" : "Nominatim/{0[0]}.{0[1]}.{0[2]}-{0[3]}".format(NOMINATIM_VERSION)}
 
     try:
         with urlrequest.urlopen(urlrequest.Request(url, headers=headers)) as response:
index b59c37bf9d5b46f1e74053ea3fd2c14621f5714b..5cfa1ab00e92a46d941bd1c55a3d7effbf6d38f1 100644 (file)
@@ -9,6 +9,7 @@ from textwrap import dedent
 from psycopg2.extras import execute_values
 
 from ..db.utils import execute_file
+from ..version import NOMINATIM_VERSION
 
 LOG = logging.getLogger()
 
@@ -258,9 +259,10 @@ def setup_website(basedir, phplib_dir, config):
                       <?php
 
                       @define('CONST_Debug', $_GET['debug'] ?? false);
-                      @define('CONST_LibDir', '{}');
+                      @define('CONST_LibDir', '{0}');
+                      @define('CONST_NominatimVersion', '{1[0]}.{1[1]}.{1[2]}-{1[3]}');
 
-                      """.format(phplib_dir))
+                      """.format(phplib_dir, NOMINATIM_VERSION))
 
     for php_name, conf_name, var_type in PHP_CONST_DEFS:
         if var_type == bool:
index 8d1c6849cf80a4fb334a9096414b2b934c197722..e7f31a12b5ab4dcebcfed07e9a32effdb1e716c4 100644 (file)
@@ -2,7 +2,15 @@
 Version information for Nominatim.
 """
 
-NOMINATIM_VERSION = "3.6.0"
+# Version information: major, minor, patch level, database patch level
+#
+# The first three numbers refer to the last released version.
+#
+# The database patch level tracks important changes between releases
+# and must always be increased when there is a change to the database or code
+# that requires a migration.
+# Released versions always have a database patch level of 0.
+NOMINATIM_VERSION = (3, 6, 0, 0)
 
 POSTGRESQL_REQUIRED_VERSION = (9, 3)
 POSTGIS_REQUIRED_VERSION = (2, 2)
index 70c106051f4a621838c1be167db4c9d1d8cee2f1..08ea83321d8ca71f747715509c385520d954cd36 100644 (file)
@@ -9,6 +9,7 @@ from pathlib import Path
 
 import pytest
 
+import nominatim.db.properties
 import nominatim.cli
 import nominatim.clicmd.api
 import nominatim.clicmd.refresh
@@ -93,6 +94,7 @@ def test_import_full(temp_db, mock_func_factory):
         mock_func_factory(nominatim.tools.database_import, 'load_data'),
         mock_func_factory(nominatim.indexer.indexer.Indexer, 'index_full'),
         mock_func_factory(nominatim.tools.refresh, 'setup_website'),
+        mock_func_factory(nominatim.db.properties, 'set_property')
     ]
 
     cf_mock = mock_func_factory(nominatim.tools.refresh, 'create_functions')
index ce81c4f3995330c9f5d6b89b462d3e05669a112c..5de686182fc2d3d90c67c68a165ed634f6e40920 100644 (file)
@@ -78,6 +78,14 @@ def test_cursor_scalar_many_rows(db):
             cur.scalar('SELECT * FROM pg_tables')
 
 
+def test_cursor_scalar_no_rows(db, table_factory):
+    table_factory('dummy')
+
+    with db.cursor() as cur:
+        with pytest.raises(RuntimeError):
+            cur.scalar('SELECT id FROM dummy')
+
+
 def test_get_pg_env_add_variable(monkeypatch):
     monkeypatch.delenv('PGPASSWORD', raising=False)
     env = get_pg_env('user=fooF')
diff --git a/test/python/test_db_properties.py b/test/python/test_db_properties.py
new file mode 100644 (file)
index 0000000..9621c68
--- /dev/null
@@ -0,0 +1,35 @@
+"""
+Tests for property table manpulation.
+"""
+import pytest
+
+from nominatim.db import properties
+
+@pytest.fixture
+def prop_table(table_factory):
+    table_factory('nominatim_properties', 'property TEXT, value TEXT')
+
+
+def test_get_property_existing(prop_table, temp_db_conn, temp_db_cursor):
+    temp_db_cursor.execute("INSERT INTO nominatim_properties VALUES('foo', 'bar')")
+
+    assert properties.get_property(temp_db_conn, 'foo') == 'bar'
+
+
+def test_get_property_unknown(prop_table, temp_db_conn, temp_db_cursor):
+    temp_db_cursor.execute("INSERT INTO nominatim_properties VALUES('other', 'bar')")
+
+    assert properties.get_property(temp_db_conn, 'foo') is None
+
+
+@pytest.mark.parametrize("prefill", (True, False))
+def test_set_property_new(prop_table, temp_db_conn, temp_db_cursor, prefill):
+    if prefill:
+        temp_db_cursor.execute("INSERT INTO nominatim_properties VALUES('something', 'bar')")
+
+    properties.set_property(temp_db_conn, 'something', 'else')
+
+    assert temp_db_cursor.scalar("""SELECT value FROM nominatim_properties
+                                    WHERE property = 'something'""") == 'else'
+
+    assert properties.get_property(temp_db_conn, 'something') == 'else'