]> git.openstreetmap.org Git - nominatim.git/commitdiff
Merge pull request #2252 from darkshredder/code-coverage
authorSarah Hoffmann <lonvia@denofr.de>
Sat, 10 Apr 2021 14:37:12 +0000 (16:37 +0200)
committerGitHub <noreply@github.com>
Sat, 10 Apr 2021 14:37:12 +0000 (16:37 +0200)
Added Code coverage support using Codecov

23 files changed:
CMakeLists.txt
ChangeLog
docs/admin/Import.md
docs/admin/Installation.md
docs/admin/Migration.md
lib-php/Geocode.php
lib-php/SearchDescription.php
lib-php/admin/query.php
lib-sql/functions/address_lookup.sql
lib-sql/functions/normalization.sql
lib-sql/functions/placex_triggers.sql
lib-sql/indices.sql
lib-sql/words.sql
nominatim/db/sql_preprocessor.py
nominatim/tools/migration.py
nominatim/tools/special_phrases.py
nominatim/version.py
test/bdd/db/query/normalization.feature
test/php/Nominatim/TokenListTest.php
vagrant/Install-on-Centos-7.sh
vagrant/Install-on-Centos-8.sh
vagrant/Install-on-Ubuntu-18.sh
vagrant/Install-on-Ubuntu-20.sh

index 6ac81c4f74b4763682131e3b867529e8417caf5e..f3247900d0826ed28915b44818af67e50c9b5af2 100644 (file)
@@ -19,7 +19,7 @@ list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
 project(nominatim)
 
 set(NOMINATIM_VERSION_MAJOR 3)
-set(NOMINATIM_VERSION_MINOR 6)
+set(NOMINATIM_VERSION_MINOR 7)
 set(NOMINATIM_VERSION_PATCH 0)
 
 set(NOMINATIM_VERSION "${NOMINATIM_VERSION_MAJOR}.${NOMINATIM_VERSION_MINOR}.${NOMINATIM_VERSION_PATCH}")
index 633d0c538f74fa1ab44f68323c659cbbe691117a..4d66ee068423b83bb4c641fb2615de422918b3ef 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,26 @@
+3.7.0
+
+ * switch to dotenv for configuration file
+ * introduce 'make install' (reorganising most of the code)
+ * introduce nominatim tool as replacement for various php scripts
+ * introduce project directories and allow multiple installations from same build
+ * clean up BDD tests: drop nose, reorganise step code
+ * simplify test database for API BDD tests and autoinstall database
+ * port most of the code for command-line tools to Python
+   (thanks to @darkshredder and @AntoJvlt)
+ * add tests for all tooling
+ * replace pyosmium-get-changes with custom internal implementation using
+   pyosmium
+ * improve search for queries with housenumber and partial terms
+ * add database versioning
+ * use jinja2 for preprocessing SQL files
+ * introduce automatic migrations
+ * reverse fix preference of interpolations over housenumbers
+ * parallelize indexing of postcodes
+ * add non-key indexes to speed up housenumber + street searches
+ * switch housenumber field in placex to save transliterated names
+
+
 3.6.0
 
  * add full support for searching by and displaying of addr:* tags
index e3a32481819d6a6b0cc93528f8c77b91eea309ab..6b417ff28c0589e7ab93e06133f80f6b06f194b2 100644 (file)
@@ -40,9 +40,9 @@ all commands from the project directory.
 
 ### Configuration setup in `.env`
 
-The Nominatim server can be customized via a `.env` in the project directory.
-This is a file in [dotenv](https://github.com/theskumar/python-dotenv) format
-which looks the same as variable settings in a standard shell environment.
+The Nominatim server can be customized via an `.env` configuration file in the
+project directory. This is a file in [dotenv](https://github.com/theskumar/python-dotenv)
+format which looks the same as variable settings in a standard shell environment.
 You can also set the same configuration via environment variables. All
 settings have a `NOMINATIM_` prefix to avoid conflicts with other environment
 variables.
@@ -283,16 +283,14 @@ address set to complement the OSM house number data in the US. You can add
 TIGER data to your own Nominatim instance by following these steps. The
 entire US adds about 10GB to your database.
 
-  1. Get preprocessed TIGER 2020 data and unpack it into your project
-     directory:
+  1. Get preprocessed TIGER 2020 data:
 
         cd $PROJECT_DIR
         wget https://nominatim.org/data/tiger2020-nominatim-preprocessed.tar.gz
-        tar xf tiger2020-nominatim-preprocessed.tar.gz
 
   2. Import the data into your Nominatim database:
 
-        nominatim add-data --tiger-data tiger
+        nominatim add-data --tiger-data tiger2020-nominatim-preprocessed.tar.gz
 
   3. Enable use of the Tiger data in your `.env` by adding:
 
index 6237a9d4a37b9026c4d1dd3938a6f3cb76e945b1..32fa8caad86d1e91a31f187766d66fc2a35a9f1e 100644 (file)
@@ -37,7 +37,7 @@ For compiling:
 
 For running Nominatim:
 
-  * [PostgreSQL](https://www.postgresql.org) (9.3+)
+  * [PostgreSQL](https://www.postgresql.org) (9.3+ will work, 11+ strongly recommended)
   * [PostGIS](https://postgis.net) (2.2+)
   * [Python 3](https://www.python.org/) (3.5+)
   * [Psycopg2](https://www.psycopg.org) (2.7+)
index 52970a33d65c078189aabf4a0537e1e15abe3385..0ca6ebf29b660399ba42528607a84c2addfa45cf 100644 (file)
@@ -4,18 +4,29 @@ Since version 3.7.0 Nominatim offers automatic migrations. Please follow
 the following steps:
 
 * stop any updates that are potentially running
-* update Nominatim to the nwer version
-* goto your project directory and run `nominatim admin --migrate`
+* update Nominatim to the newer version
+* go to your project directory and run `nominatim admin --migrate`
 * (optionally) restart updates
 
 Below you find additional migrations and hints about other structural and
-breaking changes.
+breaking changes. **Please read them before running the migration.**
 
 !!! note
     If you are migrating from a version <3.6, then you still have to follow
     the manual migration steps up to 3.6.
 
-## 3.6.0 -> master
+## 3.6.0 -> 3.7.0
+
+### New format and name of configuration file
+
+The configuration for an import is now saved in a `.env` file in the project
+directory. This file follows the dotenv format. For more information, see
+the [installation chapter](Import.md#configuration-setup-in-env).
+
+To migrate to the new system, create a new project directory, add the `.env`
+file and port your custom configuration from `settings/local.php`. Most
+settings are named similar and only have received a `NOMINATIM_` prefix.
+Use the default settings in `settings/env.defaults` as a reference.
 
 ### New location for data files
 
@@ -45,6 +56,12 @@ Try `nominatim <command> --help` for more information about each subcommand.
 `./utils/query.php` no longer exists in its old form. `nominatim search`
 provides a replacement but returns different output.
 
+### Switch to normalized house numbers
+
+The housenumber column in the placex table uses now normalized version.
+The automatic migration step will convert the column but this may take a
+very long time. It is advisable to take the machine offline while doing that.
+
 ## 3.5.0 -> 3.6.0
 
 ### Change of layout of search_name_* tables
index f638af9a9a300a927f066a59690c23b6ee58e966..ec6876faa51bbd4b64402e1abaf3450993cdc81b 100644 (file)
@@ -18,7 +18,6 @@ class Geocode
     protected $aLangPrefOrder = array();
 
     protected $aExcludePlaceIDs = array();
-    protected $bReverseInPlan = true;
 
     protected $iLimit = 20;
     protected $iFinalLimit = 10;
@@ -61,11 +60,6 @@ class Geocode
         return $this->oNormalizer->transliterate($sTerm);
     }
 
-    public function setReverseInPlan($bReverse)
-    {
-        $this->bReverseInPlan = $bReverse;
-    }
-
     public function setLanguagePreference($aLangPref)
     {
         $this->aLangPrefOrder = $aLangPref;
@@ -262,7 +256,6 @@ class Geocode
                 $oParams->getString('country'),
                 $oParams->getString('postalcode')
             );
-            $this->setReverseInPlan(false);
         } else {
             $this->setQuery($sQuery);
         }
@@ -330,7 +323,7 @@ class Geocode
         return false;
     }
 
-    public function getGroupedSearches($aSearches, $aPhrases, $oValidTokens, $bIsStructured)
+    public function getGroupedSearches($aSearches, $aPhrases, $oValidTokens)
     {
         /*
              Calculate all searches using oValidTokens i.e.
@@ -345,7 +338,7 @@ class Geocode
          */
         foreach ($aPhrases as $iPhrase => $oPhrase) {
             $aNewPhraseSearches = array();
-            $sPhraseType = $bIsStructured ? $oPhrase->getPhraseType() : '';
+            $sPhraseType = $oPhrase->getPhraseType();
 
             foreach ($oPhrase->getWordSets() as $aWordset) {
                 $aWordsetSearches = $aSearches;
@@ -388,7 +381,7 @@ class Geocode
                                 $aNewSearches = $oCurrentSearch->extendWithPartialTerm(
                                     $sToken,
                                     $oSearchTerm,
-                                    $bIsStructured,
+                                    (bool) $sPhraseType,
                                     $iPhrase,
                                     $oValidTokens->get(' '.$sToken)
                                 );
@@ -607,10 +600,8 @@ class Geocode
             // Commas are used to reduce the search space by indicating where phrases split
             if ($this->aStructuredQuery) {
                 $aInPhrases = $this->aStructuredQuery;
-                $bStructuredPhrases = true;
             } else {
                 $aInPhrases = explode(',', $sQuery);
-                $bStructuredPhrases = false;
             }
 
             Debug::printDebugArray('Search context', $oCtx);
@@ -684,9 +675,9 @@ class Geocode
 
                 Debug::newSection('Search candidates');
 
-                $aGroupedSearches = $this->getGroupedSearches($aSearches, $aPhrases, $oValidTokens, $bStructuredPhrases);
+                $aGroupedSearches = $this->getGroupedSearches($aSearches, $aPhrases, $oValidTokens);
 
-                if ($this->bReverseInPlan) {
+                if (!$this->aStructuredQuery) {
                     // Reverse phrase array and also reverse the order of the wordsets in
                     // the first and final phrase. Don't bother about phrases in the middle
                     // because order in the address doesn't matter.
@@ -695,7 +686,7 @@ class Geocode
                     if (count($aPhrases) > 1) {
                         $aPhrases[count($aPhrases)-1]->invertWordSets();
                     }
-                    $aReverseGroupedSearches = $this->getGroupedSearches($aSearches, $aPhrases, $oValidTokens, false);
+                    $aReverseGroupedSearches = $this->getGroupedSearches($aSearches, $aPhrases, $oValidTokens);
 
                     foreach ($aGroupedSearches as $aSearches) {
                         foreach ($aSearches as $aSearch) {
@@ -999,7 +990,6 @@ class Geocode
                 'Structured query' => $this->aStructuredQuery,
                 'Name keys' => Debug::fmtArrayVals($this->aLangPrefOrder),
                 'Excluded place IDs' => Debug::fmtArrayVals($this->aExcludePlaceIDs),
-                'Try reversed query'=> $this->bReverseInPlan,
                 'Limit (for searches)' => $this->iLimit,
                 'Limit (for results)'=> $this->iFinalLimit,
                 'Country codes' => Debug::fmtArrayVals($this->aCountryCodes),
index 2b39443f6e79f1b86b1571520ac0c1d4369cf6bc..dd20550214325b952452ec98de3bf4a96351071e 100644 (file)
@@ -621,7 +621,7 @@ class SearchDescription
             $aOrder[0] .= '  SELECT place_id';
             $aOrder[0] .= '  FROM placex';
             $aOrder[0] .= '  WHERE parent_place_id = search_name.place_id';
-            $aOrder[0] .= "    AND transliteration(housenumber) ~* E'".$sHouseNumberRegex."'";
+            $aOrder[0] .= "    AND housenumber ~* E'".$sHouseNumberRegex."'";
             $aOrder[0] .= '  LIMIT 1';
             $aOrder[0] .= ') ';
             // also housenumbers from interpolation lines table are needed
@@ -751,7 +751,7 @@ class SearchDescription
         $sHouseNumberRegex = '\\\\m'.$this->sHouseNumber.'\\\\M';
         $sSQL = 'SELECT place_id FROM placex ';
         $sSQL .= 'WHERE parent_place_id in ('.$sPlaceIDs.')';
-        $sSQL .= "  AND transliteration(housenumber) ~* E'".$sHouseNumberRegex."'";
+        $sSQL .= "  AND housenumber ~* E'".$sHouseNumberRegex."'";
         $sSQL .= $this->oContext->excludeSQL(' AND place_id');
 
         Debug::printSQL($sSQL);
index beb2f9ef32c034e516aaad8266b932d685b03745..35fd1184a579e7ebc0219b93a32c240465323c94 100644 (file)
@@ -79,7 +79,6 @@ if (!$oParams->hasSetAny($aSearchParams)) {
 $oGeocode = new Nominatim\Geocode($oDB);
 
 $oGeocode->setLanguagePreference($oParams->getPreferredLanguages(false));
-$oGeocode->setReverseInPlan(true);
 $oGeocode->loadParamArray($oParams);
 
 if ($oParams->getBool('search')) {
index f49bc93efd52d8fbce8e008d40817398ca038f53..5ec977d17552d7434bde9dc7fe88db9555b12c6c 100644 (file)
@@ -164,7 +164,10 @@ BEGIN
   -- POI objects in the placex table
   IF place IS NULL THEN
     SELECT parent_place_id as place_id, country_code,
-           housenumber, postcode,
+           coalesce(address->'housenumber',
+                    address->'streetnumber',
+                    address->'conscriptionnumber')::text as housenumber,
+           postcode,
            class, type,
            name, address,
            centroid
@@ -178,7 +181,7 @@ BEGIN
   -- place we should be using instead.
   IF place IS NULL THEN
     select coalesce(linked_place_id, place_id) as place_id,  country_code,
-           housenumber, postcode,
+           null::text as housenumber, postcode,
            class, type,
            null as name, address,
            null as centroid
index 6fcdf55250511077b74366651f062aac45125c55..f283f9165d9e324590ecbdaaa5fc9fc872651e16 100644 (file)
@@ -47,6 +47,25 @@ END;
 $$
 LANGUAGE plpgsql;
 
+-- Create housenumber tokens from an OSM addr:housenumber.
+-- The housnumber is split at comma and semicolon as necessary.
+-- The function returns the normalized form of the housenumber suitable
+-- for comparison.
+CREATE OR REPLACE FUNCTION create_housenumber_id(housenumber TEXT)
+  RETURNS TEXT
+  AS $$
+DECLARE
+  normtext TEXT;
+BEGIN
+  SELECT array_to_string(array_agg(trans), ';')
+    INTO normtext
+    FROM (SELECT lookup_word as trans, getorcreate_housenumber_id(lookup_word)
+          FROM (SELECT make_standard_name(h) as lookup_word
+                FROM regexp_split_to_table(housenumber, '[,;]') h) x) y;
+
+  return normtext;
+END;
+$$ LANGUAGE plpgsql STABLE STRICT;
 
 CREATE OR REPLACE FUNCTION getorcreate_housenumber_id(lookup_word TEXT)
   RETURNS INTEGER
index 086ba9300f8145d317b7e915894aa4c514f9864d..6998224e7b851893e590d9f63ca47ca6acd1b18e 100644 (file)
@@ -666,20 +666,17 @@ BEGIN
   NEW.housenumber := NULL;
   IF NEW.address is not NULL THEN
       IF NEW.address ? 'conscriptionnumber' THEN
-        i := getorcreate_housenumber_id(make_standard_name(NEW.address->'conscriptionnumber'));
         IF NEW.address ? 'streetnumber' THEN
-            i := getorcreate_housenumber_id(make_standard_name(NEW.address->'streetnumber'));
             NEW.housenumber := (NEW.address->'conscriptionnumber') || '/' || (NEW.address->'streetnumber');
         ELSE
             NEW.housenumber := NEW.address->'conscriptionnumber';
         END IF;
       ELSEIF NEW.address ? 'streetnumber' THEN
         NEW.housenumber := NEW.address->'streetnumber';
-        i := getorcreate_housenumber_id(make_standard_name(NEW.address->'streetnumber'));
       ELSEIF NEW.address ? 'housenumber' THEN
         NEW.housenumber := NEW.address->'housenumber';
-        i := getorcreate_housenumber_id(make_standard_name(NEW.housenumber));
       END IF;
+      NEW.housenumber := create_housenumber_id(NEW.housenumber);
 
       addr_street := NEW.address->'street';
       addr_place := NEW.address->'place';
index f8c9d2ce86511ef993231507883aa64998ef2eee..c121a9631b84b5d758dc552f9d45e84695bdc8be 100644 (file)
@@ -61,4 +61,11 @@ CREATE INDEX {{sql.if_index_not_exists}} idx_postcode_postcode
     ON search_name USING GIN (name_vector) WITH (fastupdate = off) {{db.tablespace.search_index}};
   CREATE INDEX {{sql.if_index_not_exists}} idx_search_name_centroid
     ON search_name USING GIST (centroid) {{db.tablespace.search_index}};
+
+  {% if postgres.has_index_non_key_column %}
+    CREATE INDEX {{sql.if_index_not_exists}} idx_placex_housenumber
+      ON placex USING btree (parent_place_id) INCLUDE (housenumber) WHERE housenumber is not null;
+    CREATE INDEX {{sql.if_index_not_exists}} idx_osmline_parent_osm_id_with_hnr
+      ON location_property_osmline USING btree(parent_place_id) INCLUDE (startnumber, endnumber);
+  {% endif %}
 {% endif %}
index ac379221c69def092639b340fbe5659b2332443e..8be178142f0d272b9d8bf6bc081b9f8341b10f7f 100644 (file)
@@ -5,7 +5,7 @@ CREATE TABLE word_frequencies AS
  GROUP BY id);
 
 select count(getorcreate_postcode_id(v)) from (select distinct address->'postcode' as v from place where address ? 'postcode') as w where v is not null;
-select count(getorcreate_housenumber_id(make_standard_name(v))) from (select distinct address->'housenumber' as v from place where address ? 'housenumber') as w;
+select count(create_housenumber_id(v)) from (select distinct address->'housenumber' as v from place where address ? 'housenumber') as w;
 
 -- copy the word frequencies
 update word set search_name_count = count from word_frequencies wf where wf.id = word.word_id;
index 852447525ab26fcb0dc80edf69bfc5096b9c2368..7ffe88818ba94a9538a9f8a61e7e8bb658e55e00 100644 (file)
@@ -49,12 +49,21 @@ def _setup_postgres_sql(conn):
     pg_version = conn.server_version_tuple()
     # CREATE INDEX IF NOT EXISTS was introduced in PG9.5.
     # Note that you need to ignore failures on older versions when
-    # unsing this construct.
+    # using this construct.
     out['if_index_not_exists'] = ' IF NOT EXISTS ' if pg_version >= (9, 5, 0) else ''
 
     return out
 
 
+def _setup_postgresql_features(conn):
+    """ Set up a dictionary with various optional Postgresql/Postgis features that
+        depend on the database version.
+    """
+    pg_version = conn.server_version_tuple()
+    return {
+        'has_index_non_key_column' : pg_version >= (11, 0, 0)
+    }
+
 class SQLPreprocessor: # pylint: disable=too-few-public-methods
     """ A environment for preprocessing SQL files from the
         lib-sql directory.
@@ -79,6 +88,7 @@ class SQLPreprocessor: # pylint: disable=too-few-public-methods
         self.env.globals['config'] = config
         self.env.globals['db'] = db_info
         self.env.globals['sql'] = _setup_postgres_sql(conn)
+        self.env.globals['postgres'] = _setup_postgresql_features(conn)
         self.env.globals['modulepath'] = config.DATABASE_MODULE_PATH or \
                                          str((config.project_dir / 'module').resolve())
 
index 756a1d3a903326a8540dcbee9ee3b545bb11d843..b5f0b80e7786328955c910e7b58ffa6b7190c71c 100644 (file)
@@ -130,3 +130,29 @@ def add_nominatim_property_table(conn, config, **_):
                                value TEXT);
                            GRANT SELECT ON TABLE nominatim_properties TO "{}";
                         """.format(config.DATABASE_WEBUSER))
+
+@_migration(3, 6, 0, 0)
+def change_housenumber_transliteration(conn, **_):
+    """ Transliterate housenumbers.
+
+        The database schema switched from saving raw housenumbers in
+        placex.housenumber to saving transliterated ones.
+    """
+    with conn.cursor() as cur:
+        cur.execute("""CREATE OR REPLACE FUNCTION create_housenumber_id(housenumber TEXT)
+                       RETURNS TEXT AS $$
+                       DECLARE
+                         normtext TEXT;
+                       BEGIN
+                         SELECT array_to_string(array_agg(trans), ';')
+                           INTO normtext
+                           FROM (SELECT lookup_word as trans, getorcreate_housenumber_id(lookup_word)
+                                 FROM (SELECT make_standard_name(h) as lookup_word
+                                       FROM regexp_split_to_table(housenumber, '[,;]') h) x) y;
+                         return normtext;
+                       END;
+                       $$ LANGUAGE plpgsql STABLE STRICT;""")
+        cur.execute("DELETE FROM word WHERE class = 'place' and type = 'house'")
+        cur.execute("""UPDATE placex
+                       SET housenumber = create_housenumber_id(housenumber)
+                       WHERE housenumber is not null""")
index fd46a18d827810aaf11a380a9cee659d3187a3fb..e2ec8ce4fa82a75ed8d72f52e1a82bf5afd3f1f8 100644 (file)
@@ -80,7 +80,7 @@ class SpecialPhrasesImporter():
             'et', 'eu', 'fa', 'fi', 'fr', 'gl', 'hr', 'hu',
             'ia', 'is', 'it', 'ja', 'mk', 'nl', 'no', 'pl',
             'ps', 'pt', 'ru', 'sk', 'sl', 'sv', 'uk', 'vi']
-        return self.config.LANGUAGES or default_languages
+        return self.config.LANGUAGES.split(',') or default_languages
 
     @staticmethod
     def _get_wiki_content(lang):
index e7f31a12b5ab4dcebcfed07e9a32effdb1e716c4..52550d19a3a4e781413a21ad3339daf5c5b7bfaa 100644 (file)
@@ -10,7 +10,7 @@ Version information for Nominatim.
 # 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)
+NOMINATIM_VERSION = (3, 7, 0, 0)
 
 POSTGRESQL_REQUIRED_VERSION = (9, 3)
 POSTGIS_REQUIRED_VERSION = (2, 2)
index 3205264753f1c67cef5a0beefb2af0e5425df293..8a324a229b86bfaa450a35d5d095bed647d483d3 100644 (file)
@@ -137,7 +137,7 @@ Feature: Import and search of names
          | ID | osm_type | osm_id |
          | 0  | R        | 1 |
 
-     Scenario: Unprintable characters in postcodes are ignored
+    Scenario: Unprintable characters in postcodes are ignored
         Given the named places
             | osm  | class   | type   | address |
             | N234 | amenity | prison | 'postcode' : u'1234\u200e' |
@@ -146,3 +146,60 @@ Feature: Import and search of names
         Then results contain
          | ID | osm_type |
          | 0  | P        |
+
+    Scenario Outline: Housenumbers with special characters are found
+        Given the grid
+            | 1 |  |   |  | 2 |
+            |   |  | 9 |  |   |
+        And the places
+            | osm | class   | type    | name    | geometry |
+            | W1  | highway | primary | Main St | 1,2      |
+        And the places
+            | osm | class    | type | housenr | geometry |
+            | N1  | building | yes  | <nr>    | 9        |
+        When importing
+        And searching for "Main St <nr>"
+        Then results contain
+         | osm_type | osm_id | name |
+         | N        | 1      | <nr>, Main St |
+
+    Examples:
+        | nr |
+        | 1  |
+        | 3456 |
+        | 1 a |
+        | 56b |
+        | 1 A |
+        | 2號 |
+        | 1Б  |
+        | 1 к1 |
+        | 23-123 |
+
+    Scenario Outline: Housenumbers in lists are found
+        Given the grid
+            | 1 |  |   |  | 2 |
+            |   |  | 9 |  |   |
+        And the places
+            | osm | class   | type    | name    | geometry |
+            | W1  | highway | primary | Main St | 1,2      |
+        And the places
+            | osm | class    | type | housenr   | geometry |
+            | N1  | building | yes  | <nr-list> | 9        |
+        When importing
+        And searching for "Main St <nr>"
+        Then results contain
+         | osm_type | osm_id | name |
+         | N        | 1      | <nr-list>, Main St |
+
+    Examples:
+        | nr-list    | nr |
+        | 1,2,3      | 1  |
+        | 1,2,3      | 2  |
+        | 1, 2, 3    | 3  |
+        | 45 ;67;3   | 45 |
+        | 45 ;67;3   | 67 |
+        | 1a;1k      | 1a |
+        | 1a;1k      | 1k |
+        | 34/678     | 34 |
+        | 34/678     | 678 |
+        | 34/678     | 34/678 |
index 3ef4d84d715df5bc4ecc8776a5986190dcff498d..14a595ea9f3c115da1e8063e3436739174699e18 100644 (file)
@@ -77,6 +77,15 @@ class TokenTest extends \PHPUnit\Framework\TestCase
                                                          'type' => 'house'
                                                         ));
                     }
+                    if (preg_match('/hauptstr/', $sql)) {
+                        $aResults[] = $this->wordResult(array(
+                                                         'word_id' => 999,
+                                                         'word_token' => 'hauptstr',
+                                                         'class' => 'place',
+                                                         'type' => 'street',
+                                                         'operator' => true
+                                                        ));
+                    }
                     if (preg_match('/64286/', $sql)) {
                         $aResults[] = $this->wordResult(array(
                                                          'word_id' => 999,
@@ -116,11 +125,12 @@ class TokenTest extends \PHPUnit\Framework\TestCase
 
         $TL = new TokenList;
         $TL->addTokensFromDB($oDbStub, $aTokens, $aCountryCodes, $sNormQuery, $this->oNormalizer);
-        $this->assertEquals(4, $TL->count());
+        $this->assertEquals(5, $TL->count());
 
         $this->assertEquals(array(new Token\HouseNumber(999, '1051')), $TL->get('1051'));
         $this->assertEquals(array(new Token\Country(999, 'de')), $TL->get('alemagne'));
         $this->assertEquals(array(new Token\Postcode(999, '64286')), $TL->get('64286'));
         $this->assertEquals(array(new Token\Word(999, true, 533, 0)), $TL->get('darmstadt'));
+        $this->assertEquals(array(new Token\SpecialTerm(999, 'place', 'street', true)), $TL->get('hauptstr'));
     }
 }
index fae961f1795afd94aac6b7348ff660a4d368e07e..32cd3a308962fdf4d8e8a6b183a4efcc23c51fcf 100755 (executable)
@@ -145,7 +145,7 @@ fi                                 #DOCS:
 # -------------------------------
 #
 # The webserver should serve the php scripts from the website directory of your
-# [project directory](../admin/import.md#creating-the-project-directory).
+# [project directory](../admin/Import.md#creating-the-project-directory).
 # Therefore set up a project directory and populate the website directory:
 #
     mkdir $USERHOME/nominatim-project
index ac3a1b6087f08b09cc4877675a327a3071bb4dca..1e028b65ee3bd9b8676b020b68915df9cf01e7a5 100755 (executable)
@@ -139,7 +139,7 @@ fi                                 #DOCS:
 # -------------------------------
 #
 # The webserver should serve the php scripts from the website directory of your
-# [project directory](../admin/import.md#creating-the-project-directory).
+# [project directory](../admin/Import.md#creating-the-project-directory).
 # Therefore set up a project directory and populate the website directory:
 #
     mkdir $USERHOME/nominatim-project
index 9fda122ee8f969ada75738321053aee0f70d2118..36e28ca1071fb6b43c85bb0d8ef9273d21e472e0 100755 (executable)
@@ -133,7 +133,7 @@ fi                                 #DOCS:
 # ======================
 #
 # The webserver should serve the php scripts from the website directory of your
-# [project directory](../admin/import.md#creating-the-project-directory).
+# [project directory](../admin/Import.md#creating-the-project-directory).
 # Therefore set up a project directory and populate the website directory:
 
     mkdir $USERHOME/nominatim-project
index 292947d179ec91ecd12a265e852c98be5711a215..1e15f850c5c24bb90170e7ee3c8fe5fd3984c1a6 100755 (executable)
@@ -130,7 +130,7 @@ fi                                 #DOCS:
 # ======================
 #
 # The webserver should serve the php scripts from the website directory of your
-# [project directory](../admin/import.md#creating-the-project-directory).
+# [project directory](../admin/Import.md#creating-the-project-directory).
 # Therefore set up a project directory and populate the website directory:
 
     mkdir $USERHOME/nominatim-project