3 namespace Nominatim\Setup;
 
   5 require_once(CONST_BasePath.'/lib/setup/AddressLevelParser.php');
 
   6 require_once(CONST_BasePath.'/lib/Shell.php');
 
  10     protected $iCacheMemory;
 
  11     protected $iInstances;
 
  12     protected $sModulePath;
 
  16     protected $sIgnoreErrors;
 
  17     protected $bEnableDiffUpdates;
 
  18     protected $bEnableDebugStatements;
 
  19     protected $bNoPartitions;
 
  21     protected $oDB = null;
 
  23     public function __construct(array $aCMDResult)
 
  25         // by default, use all but one processor, but never more than 15.
 
  26         $this->iInstances = isset($aCMDResult['threads'])
 
  27             ? $aCMDResult['threads']
 
  28             : (min(16, getProcessorCount()) - 1);
 
  30         if ($this->iInstances < 1) {
 
  31             $this->iInstances = 1;
 
  32             warn('resetting threads to '.$this->iInstances);
 
  35         if (isset($aCMDResult['osm2pgsql-cache'])) {
 
  36             $this->iCacheMemory = $aCMDResult['osm2pgsql-cache'];
 
  37         } elseif (!is_null(CONST_Osm2pgsql_Flatnode_File)) {
 
  38             // When flatnode files are enabled then disable cache per default.
 
  39             $this->iCacheMemory = 0;
 
  41             // Otherwise: Assume we can steal all the cache memory in the box.
 
  42             $this->iCacheMemory = getCacheMemoryMB();
 
  45         $this->sModulePath = CONST_Database_Module_Path;
 
  46         info('module path: ' . $this->sModulePath);
 
  48         // parse database string
 
  49         $this->aDSNInfo = \Nominatim\DB::parseDSN(CONST_Database_DSN);
 
  50         if (!isset($this->aDSNInfo['port'])) {
 
  51             $this->aDSNInfo['port'] = 5432;
 
  54         // setting member variables based on command line options stored in $aCMDResult
 
  55         $this->bQuiet = isset($aCMDResult['quiet']) && $aCMDResult['quiet'];
 
  56         $this->bVerbose = $aCMDResult['verbose'];
 
  58         //setting default values which are not set by the update.php array
 
  59         if (isset($aCMDResult['ignore-errors'])) {
 
  60             $this->sIgnoreErrors = $aCMDResult['ignore-errors'];
 
  62             $this->sIgnoreErrors = false;
 
  64         if (isset($aCMDResult['enable-debug-statements'])) {
 
  65             $this->bEnableDebugStatements = $aCMDResult['enable-debug-statements'];
 
  67             $this->bEnableDebugStatements = false;
 
  69         if (isset($aCMDResult['no-partitions'])) {
 
  70             $this->bNoPartitions = $aCMDResult['no-partitions'];
 
  72             $this->bNoPartitions = false;
 
  74         if (isset($aCMDResult['enable-diff-updates'])) {
 
  75             $this->bEnableDiffUpdates = $aCMDResult['enable-diff-updates'];
 
  77             $this->bEnableDiffUpdates = false;
 
  80         $this->bDrop = isset($aCMDResult['drop']) && $aCMDResult['drop'];
 
  83     public function createDB()
 
  86         $oDB = new \Nominatim\DB;
 
  88         if ($oDB->checkConnection()) {
 
  89             fail('database already exists ('.CONST_Database_DSN.')');
 
  92         $oCmd = (new \Nominatim\Shell('createdb'))
 
  93                 ->addParams('-E', 'UTF-8')
 
  94                 ->addParams('-p', $this->aDSNInfo['port']);
 
  96         if (isset($this->aDSNInfo['username'])) {
 
  97             $oCmd->addParams('-U', $this->aDSNInfo['username']);
 
  99         if (isset($this->aDSNInfo['password'])) {
 
 100             $oCmd->addEnvPair('PGPASSWORD', $this->aDSNInfo['password']);
 
 102         if (isset($this->aDSNInfo['hostspec'])) {
 
 103             $oCmd->addParams('-h', $this->aDSNInfo['hostspec']);
 
 105         $oCmd->addParams($this->aDSNInfo['database']);
 
 107         $result = $oCmd->run();
 
 108         if ($result != 0) fail('Error executing external command: '.$oCmd->escapedCmd());
 
 111     public function setupDB()
 
 115         $fPostgresVersion = $this->db()->getPostgresVersion();
 
 116         echo 'Postgres version found: '.$fPostgresVersion."\n";
 
 118         if ($fPostgresVersion < 9.03) {
 
 119             fail('Minimum supported version of Postgresql is 9.3.');
 
 122         $this->pgsqlRunScript('CREATE EXTENSION IF NOT EXISTS hstore');
 
 123         $this->pgsqlRunScript('CREATE EXTENSION IF NOT EXISTS postgis');
 
 125         $fPostgisVersion = $this->db()->getPostgisVersion();
 
 126         echo 'Postgis version found: '.$fPostgisVersion."\n";
 
 128         if ($fPostgisVersion < 2.2) {
 
 129             echo "Minimum required Postgis version 2.2\n";
 
 133         $i = $this->db()->getOne("select count(*) from pg_user where usename = '".CONST_Database_Web_User."'");
 
 135             echo "\nERROR: Web user '".CONST_Database_Web_User."' does not exist. Create it with:\n";
 
 136             echo "\n          createuser ".CONST_Database_Web_User."\n\n";
 
 140         // Try accessing the C module, so we know early if something is wrong
 
 141         checkModulePresence(); // raises exception on failure
 
 143         if (!file_exists(CONST_ExtraDataPath.'/country_osm_grid.sql.gz')) {
 
 144             echo 'Error: you need to download the country_osm_grid first:';
 
 145             echo "\n    wget -O ".CONST_ExtraDataPath."/country_osm_grid.sql.gz https://www.nominatim.org/data/country_grid.sql.gz\n";
 
 148         $this->pgsqlRunScriptFile(CONST_BasePath.'/data/country_name.sql');
 
 149         $this->pgsqlRunScriptFile(CONST_ExtraDataPath.'/country_osm_grid.sql.gz');
 
 150         $this->pgsqlRunScriptFile(CONST_BasePath.'/data/gb_postcode_table.sql');
 
 151         $this->pgsqlRunScriptFile(CONST_BasePath.'/data/us_postcode_table.sql');
 
 153         $sPostcodeFilename = CONST_BasePath.'/data/gb_postcode_data.sql.gz';
 
 154         if (file_exists($sPostcodeFilename)) {
 
 155             $this->pgsqlRunScriptFile($sPostcodeFilename);
 
 157             warn('optional external GB postcode table file ('.$sPostcodeFilename.') not found. Skipping.');
 
 160         $sPostcodeFilename = CONST_BasePath.'/data/us_postcode_data.sql.gz';
 
 161         if (file_exists($sPostcodeFilename)) {
 
 162             $this->pgsqlRunScriptFile($sPostcodeFilename);
 
 164             warn('optional external US postcode table file ('.$sPostcodeFilename.') not found. Skipping.');
 
 167         if ($this->bNoPartitions) {
 
 168             $this->pgsqlRunScript('update country_name set partition = 0');
 
 172     public function importData($sOSMFile)
 
 176         if (!file_exists(CONST_Osm2pgsql_Binary)) {
 
 177             echo "Check CONST_Osm2pgsql_Binary in your local settings file.\n";
 
 178             echo "Normally you should not need to set this manually.\n";
 
 179             fail("osm2pgsql not found in '".CONST_Osm2pgsql_Binary."'");
 
 182         $oCmd = new \Nominatim\Shell(CONST_Osm2pgsql_Binary);
 
 183         $oCmd->addParams('--style', CONST_Import_Style);
 
 185         if (!is_null(CONST_Osm2pgsql_Flatnode_File) && CONST_Osm2pgsql_Flatnode_File) {
 
 186             $oCmd->addParams('--flat-nodes', CONST_Osm2pgsql_Flatnode_File);
 
 188         if (CONST_Tablespace_Osm2pgsql_Data) {
 
 189             $oCmd->addParams('--tablespace-slim-data', CONST_Tablespace_Osm2pgsql_Data);
 
 191         if (CONST_Tablespace_Osm2pgsql_Index) {
 
 192             $oCmd->addParams('--tablespace-slim-index', CONST_Tablespace_Osm2pgsql_Index);
 
 194         if (CONST_Tablespace_Place_Data) {
 
 195             $oCmd->addParams('--tablespace-main-data', CONST_Tablespace_Place_Data);
 
 197         if (CONST_Tablespace_Place_Index) {
 
 198             $oCmd->addParams('--tablespace-main-index', CONST_Tablespace_Place_Index);
 
 200         $oCmd->addParams('--latlong', '--slim', '--create');
 
 201         $oCmd->addParams('--output', 'gazetteer');
 
 202         $oCmd->addParams('--hstore');
 
 203         $oCmd->addParams('--number-processes', 1);
 
 204         $oCmd->addParams('--cache', $this->iCacheMemory);
 
 205         $oCmd->addParams('--port', $this->aDSNInfo['port']);
 
 207         if (isset($this->aDSNInfo['username'])) {
 
 208             $oCmd->addParams('--username', $this->aDSNInfo['username']);
 
 210         if (isset($this->aDSNInfo['password'])) {
 
 211             $oCmd->addEnvPair('PGPASSWORD', $this->aDSNInfo['password']);
 
 213         if (isset($this->aDSNInfo['hostspec'])) {
 
 214             $oCmd->addParams('--host', $this->aDSNInfo['hostspec']);
 
 216         $oCmd->addParams('--database', $this->aDSNInfo['database']);
 
 217         $oCmd->addParams($sOSMFile);
 
 220         if (!$this->sIgnoreErrors && !$this->db()->getRow('select * from place limit 1')) {
 
 225             $this->dropTable('planet_osm_nodes');
 
 226             $this->removeFlatnodeFile();
 
 230     public function createFunctions()
 
 232         info('Create Functions');
 
 234         // Try accessing the C module, so we know early if something is wrong
 
 235         checkModulePresence(); // raises exception on failure
 
 237         $this->createSqlFunctions();
 
 240     public function createTables($bReverseOnly = false)
 
 242         info('Create Tables');
 
 244         $sTemplate = file_get_contents(CONST_BasePath.'/sql/tables.sql');
 
 245         $sTemplate = $this->replaceSqlPatterns($sTemplate);
 
 247         $this->pgsqlRunScript($sTemplate, false);
 
 250             $this->dropTable('search_name');
 
 253         $oAlParser = new AddressLevelParser(CONST_Address_Level_Config);
 
 254         $oAlParser->createTable($this->db(), 'address_levels');
 
 257     public function createTableTriggers()
 
 259         info('Create Tables');
 
 261         $sTemplate = file_get_contents(CONST_BasePath.'/sql/table-triggers.sql');
 
 262         $sTemplate = $this->replaceSqlPatterns($sTemplate);
 
 264         $this->pgsqlRunScript($sTemplate, false);
 
 267     public function createPartitionTables()
 
 269         info('Create Partition Tables');
 
 271         $sTemplate = file_get_contents(CONST_BasePath.'/sql/partition-tables.src.sql');
 
 272         $sTemplate = $this->replaceSqlPatterns($sTemplate);
 
 274         $this->pgsqlRunPartitionScript($sTemplate);
 
 277     public function createPartitionFunctions()
 
 279         info('Create Partition Functions');
 
 281         $sTemplate = file_get_contents(CONST_BasePath.'/sql/partition-functions.src.sql');
 
 282         $this->pgsqlRunPartitionScript($sTemplate);
 
 285     public function importWikipediaArticles()
 
 287         $sWikiArticlesFile = CONST_Wikipedia_Data_Path.'/wikimedia-importance.sql.gz';
 
 288         if (file_exists($sWikiArticlesFile)) {
 
 289             info('Importing wikipedia articles and redirects');
 
 290             $this->dropTable('wikipedia_article');
 
 291             $this->dropTable('wikipedia_redirect');
 
 292             $this->pgsqlRunScriptFile($sWikiArticlesFile);
 
 294             warn('wikipedia importance dump file not found - places will have default importance');
 
 298     public function loadData($bDisableTokenPrecalc)
 
 300         info('Drop old Data');
 
 304         $oDB->exec('TRUNCATE word');
 
 306         $oDB->exec('TRUNCATE placex');
 
 308         $oDB->exec('TRUNCATE location_property_osmline');
 
 310         $oDB->exec('TRUNCATE place_addressline');
 
 312         $oDB->exec('TRUNCATE location_area');
 
 314         if (!$this->dbReverseOnly()) {
 
 315             $oDB->exec('TRUNCATE search_name');
 
 318         $oDB->exec('TRUNCATE search_name_blank');
 
 320         $oDB->exec('DROP SEQUENCE seq_place');
 
 322         $oDB->exec('CREATE SEQUENCE seq_place start 100000');
 
 325         $sSQL = 'select distinct partition from country_name';
 
 326         $aPartitions = $oDB->getCol($sSQL);
 
 328         if (!$this->bNoPartitions) $aPartitions[] = 0;
 
 329         foreach ($aPartitions as $sPartition) {
 
 330             $oDB->exec('TRUNCATE location_road_'.$sPartition);
 
 334         // used by getorcreate_word_id to ignore frequent partial words
 
 335         $sSQL = 'CREATE OR REPLACE FUNCTION get_maxwordfreq() RETURNS integer AS ';
 
 336         $sSQL .= '$$ SELECT '.CONST_Max_Word_Frequency.' as maxwordfreq; $$ LANGUAGE SQL IMMUTABLE';
 
 340         // pre-create the word list
 
 341         if (!$bDisableTokenPrecalc) {
 
 342             info('Loading word list');
 
 343             $this->pgsqlRunScriptFile(CONST_BasePath.'/data/words.sql');
 
 347         $sColumns = 'osm_type, osm_id, class, type, name, admin_level, address, extratags, geometry';
 
 349         $aDBInstances = array();
 
 350         $iLoadThreads = max(1, $this->iInstances - 1);
 
 351         for ($i = 0; $i < $iLoadThreads; $i++) {
 
 352             // https://secure.php.net/manual/en/function.pg-connect.php
 
 353             $DSN = CONST_Database_DSN;
 
 354             $DSN = preg_replace('/^pgsql:/', '', $DSN);
 
 355             $DSN = preg_replace('/;/', ' ', $DSN);
 
 356             $aDBInstances[$i] = pg_connect($DSN, PGSQL_CONNECT_FORCE_NEW);
 
 357             pg_ping($aDBInstances[$i]);
 
 360         for ($i = 0; $i < $iLoadThreads; $i++) {
 
 361             $sSQL = "INSERT INTO placex ($sColumns) SELECT $sColumns FROM place WHERE osm_id % $iLoadThreads = $i";
 
 362             $sSQL .= " and not (class='place' and type='houses' and osm_type='W'";
 
 363             $sSQL .= "          and ST_GeometryType(geometry) = 'ST_LineString')";
 
 364             $sSQL .= ' and ST_IsValid(geometry)';
 
 365             if ($this->bVerbose) echo "$sSQL\n";
 
 366             if (!pg_send_query($aDBInstances[$i], $sSQL)) {
 
 367                 fail(pg_last_error($aDBInstances[$i]));
 
 371         // last thread for interpolation lines
 
 372         // https://secure.php.net/manual/en/function.pg-connect.php
 
 373         $DSN = CONST_Database_DSN;
 
 374         $DSN = preg_replace('/^pgsql:/', '', $DSN);
 
 375         $DSN = preg_replace('/;/', ' ', $DSN);
 
 376         $aDBInstances[$iLoadThreads] = pg_connect($DSN, PGSQL_CONNECT_FORCE_NEW);
 
 377         pg_ping($aDBInstances[$iLoadThreads]);
 
 378         $sSQL = 'insert into location_property_osmline';
 
 379         $sSQL .= ' (osm_id, address, linegeo)';
 
 380         $sSQL .= ' SELECT osm_id, address, geometry from place where ';
 
 381         $sSQL .= "class='place' and type='houses' and osm_type='W' and ST_GeometryType(geometry) = 'ST_LineString'";
 
 382         if ($this->bVerbose) echo "$sSQL\n";
 
 383         if (!pg_send_query($aDBInstances[$iLoadThreads], $sSQL)) {
 
 384             fail(pg_last_error($aDBInstances[$iLoadThreads]));
 
 388         for ($i = 0; $i <= $iLoadThreads; $i++) {
 
 389             while (($hPGresult = pg_get_result($aDBInstances[$i])) !== false) {
 
 390                 $resultStatus = pg_result_status($hPGresult);
 
 391                 // PGSQL_EMPTY_QUERY, PGSQL_COMMAND_OK, PGSQL_TUPLES_OK,
 
 392                 // PGSQL_COPY_OUT, PGSQL_COPY_IN, PGSQL_BAD_RESPONSE,
 
 393                 // PGSQL_NONFATAL_ERROR and PGSQL_FATAL_ERROR
 
 394                 // echo 'Query result ' . $i . ' is: ' . $resultStatus . "\n";
 
 395                 if ($resultStatus != PGSQL_COMMAND_OK && $resultStatus != PGSQL_TUPLES_OK) {
 
 396                     $resultError = pg_result_error($hPGresult);
 
 397                     echo '-- error text ' . $i . ': ' . $resultError . "\n";
 
 403             fail('SQL errors loading placex and/or location_property_osmline tables');
 
 406         for ($i = 0; $i < $this->iInstances; $i++) {
 
 407             pg_close($aDBInstances[$i]);
 
 411         info('Reanalysing database');
 
 412         $this->pgsqlRunScript('ANALYSE');
 
 414         $sDatabaseDate = getDatabaseDate($oDB);
 
 415         $oDB->exec('TRUNCATE import_status');
 
 416         if (!$sDatabaseDate) {
 
 417             warn('could not determine database date.');
 
 419             $sSQL = "INSERT INTO import_status (lastimportdate) VALUES('".$sDatabaseDate."')";
 
 421             echo "Latest data imported from $sDatabaseDate.\n";
 
 425     public function importTigerData()
 
 427         info('Import Tiger data');
 
 429         $aFilenames = glob(CONST_Tiger_Data_Path.'/*.sql');
 
 430         info('Found '.count($aFilenames).' SQL files in path '.CONST_Tiger_Data_Path);
 
 431         if (empty($aFilenames)) {
 
 432             warn('Tiger data import selected but no files found in path '.CONST_Tiger_Data_Path);
 
 435         $sTemplate = file_get_contents(CONST_BasePath.'/sql/tiger_import_start.sql');
 
 436         $sTemplate = $this->replaceSqlPatterns($sTemplate);
 
 438         $this->pgsqlRunScript($sTemplate, false);
 
 440         $aDBInstances = array();
 
 441         for ($i = 0; $i < $this->iInstances; $i++) {
 
 442             // https://secure.php.net/manual/en/function.pg-connect.php
 
 443             $DSN = CONST_Database_DSN;
 
 444             $DSN = preg_replace('/^pgsql:/', '', $DSN);
 
 445             $DSN = preg_replace('/;/', ' ', $DSN);
 
 446             $aDBInstances[$i] = pg_connect($DSN, PGSQL_CONNECT_FORCE_NEW | PGSQL_CONNECT_ASYNC);
 
 447             pg_ping($aDBInstances[$i]);
 
 450         foreach ($aFilenames as $sFile) {
 
 452             $hFile = fopen($sFile, 'r');
 
 453             $sSQL = fgets($hFile, 100000);
 
 456                 for ($i = 0; $i < $this->iInstances; $i++) {
 
 457                     if (!pg_connection_busy($aDBInstances[$i])) {
 
 458                         while (pg_get_result($aDBInstances[$i]));
 
 459                         $sSQL = fgets($hFile, 100000);
 
 461                         if (!pg_send_query($aDBInstances[$i], $sSQL)) fail(pg_last_error($aDBInstances[$i]));
 
 463                         if ($iLines == 1000) {
 
 476                 for ($i = 0; $i < $this->iInstances; $i++) {
 
 477                     if (pg_connection_busy($aDBInstances[$i])) $bAnyBusy = true;
 
 484         for ($i = 0; $i < $this->iInstances; $i++) {
 
 485             pg_close($aDBInstances[$i]);
 
 488         info('Creating indexes on Tiger data');
 
 489         $sTemplate = file_get_contents(CONST_BasePath.'/sql/tiger_import_finish.sql');
 
 490         $sTemplate = $this->replaceSqlPatterns($sTemplate);
 
 492         $this->pgsqlRunScript($sTemplate, false);
 
 495     public function calculatePostcodes($bCMDResultAll)
 
 497         info('Calculate Postcodes');
 
 498         $this->db()->exec('TRUNCATE location_postcode');
 
 500         $sSQL  = 'INSERT INTO location_postcode';
 
 501         $sSQL .= ' (place_id, indexed_status, country_code, postcode, geometry) ';
 
 502         $sSQL .= "SELECT nextval('seq_place'), 1, country_code,";
 
 503         $sSQL .= "       upper(trim (both ' ' from address->'postcode')) as pc,";
 
 504         $sSQL .= '       ST_Centroid(ST_Collect(ST_Centroid(geometry)))';
 
 505         $sSQL .= '  FROM placex';
 
 506         $sSQL .= " WHERE address ? 'postcode' AND address->'postcode' NOT SIMILAR TO '%(,|;)%'";
 
 507         $sSQL .= '       AND geometry IS NOT null';
 
 508         $sSQL .= ' GROUP BY country_code, pc';
 
 509         $this->db()->exec($sSQL);
 
 511         // only add postcodes that are not yet available in OSM
 
 512         $sSQL  = 'INSERT INTO location_postcode';
 
 513         $sSQL .= ' (place_id, indexed_status, country_code, postcode, geometry) ';
 
 514         $sSQL .= "SELECT nextval('seq_place'), 1, 'us', postcode,";
 
 515         $sSQL .= '       ST_SetSRID(ST_Point(x,y),4326)';
 
 516         $sSQL .= '  FROM us_postcode WHERE postcode NOT IN';
 
 517         $sSQL .= '        (SELECT postcode FROM location_postcode';
 
 518         $sSQL .= "          WHERE country_code = 'us')";
 
 519         $this->db()->exec($sSQL);
 
 521         // add missing postcodes for GB (if available)
 
 522         $sSQL  = 'INSERT INTO location_postcode';
 
 523         $sSQL .= ' (place_id, indexed_status, country_code, postcode, geometry) ';
 
 524         $sSQL .= "SELECT nextval('seq_place'), 1, 'gb', postcode, geometry";
 
 525         $sSQL .= '  FROM gb_postcode WHERE postcode NOT IN';
 
 526         $sSQL .= '           (SELECT postcode FROM location_postcode';
 
 527         $sSQL .= "             WHERE country_code = 'gb')";
 
 528         $this->db()->exec($sSQL);
 
 530         if (!$bCMDResultAll) {
 
 531             $sSQL = "DELETE FROM word WHERE class='place' and type='postcode'";
 
 532             $sSQL .= 'and word NOT IN (SELECT postcode FROM location_postcode)';
 
 533             $this->db()->exec($sSQL);
 
 536         $sSQL = 'SELECT count(getorcreate_postcode_id(v)) FROM ';
 
 537         $sSQL .= '(SELECT distinct(postcode) as v FROM location_postcode) p';
 
 538         $this->db()->exec($sSQL);
 
 541     public function index($bIndexNoanalyse)
 
 543         checkModulePresence(); // raises exception on failure
 
 545         $oBaseCmd = (new \Nominatim\Shell(CONST_BasePath.'/nominatim/nominatim.py'))
 
 546                     ->addParams('--database', $this->aDSNInfo['database'])
 
 547                     ->addParams('--port', $this->aDSNInfo['port'])
 
 548                     ->addParams('--threads', $this->iInstances);
 
 550         if (!$this->bQuiet) {
 
 551             $oBaseCmd->addParams('-v');
 
 553         if ($this->bVerbose) {
 
 554             $oBaseCmd->addParams('-v');
 
 556         if (isset($this->aDSNInfo['hostspec'])) {
 
 557             $oBaseCmd->addParams('--host', $this->aDSNInfo['hostspec']);
 
 559         if (isset($this->aDSNInfo['username'])) {
 
 560             $oBaseCmd->addParams('--user', $this->aDSNInfo['username']);
 
 562         if (isset($this->aDSNInfo['password'])) {
 
 563             $oBaseCmd->addEnvPair('PGPASSWORD', $this->aDSNInfo['password']);
 
 566         info('Index ranks 0 - 4');
 
 567         $oCmd = (clone $oBaseCmd)->addParams('--maxrank', 4);
 
 568         echo $oCmd->escapedCmd();
 
 570         $iStatus = $oCmd->run();
 
 572             fail('error status ' . $iStatus . ' running nominatim!');
 
 574         if (!$bIndexNoanalyse) $this->pgsqlRunScript('ANALYSE');
 
 576         info('Index ranks 5 - 25');
 
 577         $oCmd = (clone $oBaseCmd)->addParams('--minrank', 5, '--maxrank', 25);
 
 578         $iStatus = $oCmd->run();
 
 580             fail('error status ' . $iStatus . ' running nominatim!');
 
 582         if (!$bIndexNoanalyse) $this->pgsqlRunScript('ANALYSE');
 
 584         info('Index ranks 26 - 30');
 
 585         $oCmd = (clone $oBaseCmd)->addParams('--minrank', 26);
 
 586         $iStatus = $oCmd->run();
 
 588             fail('error status ' . $iStatus . ' running nominatim!');
 
 591         info('Index postcodes');
 
 592         $sSQL = 'UPDATE location_postcode SET indexed_status = 0';
 
 593         $this->db()->exec($sSQL);
 
 596     public function createSearchIndices()
 
 598         info('Create Search indices');
 
 600         $sSQL = 'SELECT relname FROM pg_class, pg_index ';
 
 601         $sSQL .= 'WHERE pg_index.indisvalid = false AND pg_index.indexrelid = pg_class.oid';
 
 602         $aInvalidIndices = $this->db()->getCol($sSQL);
 
 604         foreach ($aInvalidIndices as $sIndexName) {
 
 605             info("Cleaning up invalid index $sIndexName");
 
 606             $this->db()->exec("DROP INDEX $sIndexName;");
 
 609         $sTemplate = file_get_contents(CONST_BasePath.'/sql/indices.src.sql');
 
 611             $sTemplate .= file_get_contents(CONST_BasePath.'/sql/indices_updates.src.sql');
 
 613         if (!$this->dbReverseOnly()) {
 
 614             $sTemplate .= file_get_contents(CONST_BasePath.'/sql/indices_search.src.sql');
 
 616         $sTemplate = $this->replaceSqlPatterns($sTemplate);
 
 618         $this->pgsqlRunScript($sTemplate);
 
 621     public function createCountryNames()
 
 623         info('Create search index for default country names');
 
 625         $this->pgsqlRunScript("select getorcreate_country(make_standard_name('uk'), 'gb')");
 
 626         $this->pgsqlRunScript("select getorcreate_country(make_standard_name('united states'), 'us')");
 
 627         $this->pgsqlRunScript('select count(*) from (select getorcreate_country(make_standard_name(country_code), country_code) from country_name where country_code is not null) as x');
 
 628         $this->pgsqlRunScript("select count(*) from (select getorcreate_country(make_standard_name(name->'name'), country_code) from country_name where name ? 'name') as x");
 
 629         $sSQL = 'select count(*) from (select getorcreate_country(make_standard_name(v),'
 
 630             .'country_code) from (select country_code, skeys(name) as k, svals(name) as v from country_name) x where k ';
 
 631         if (CONST_Languages) {
 
 634             foreach (explode(',', CONST_Languages) as $sLang) {
 
 635                 $sSQL .= $sDelim."'name:$sLang'";
 
 640             // all include all simple name tags
 
 641             $sSQL .= "like 'name:%'";
 
 644         $this->pgsqlRunScript($sSQL);
 
 647     public function drop()
 
 649         info('Drop tables only required for updates');
 
 651         // The implementation is potentially a bit dangerous because it uses
 
 652         // a positive selection of tables to keep, and deletes everything else.
 
 653         // Including any tables that the unsuspecting user might have manually
 
 654         // created. USE AT YOUR OWN PERIL.
 
 655         // tables we want to keep. everything else goes.
 
 656         $aKeepTables = array(
 
 662                         'location_property*',
 
 675         $aDropTables = array();
 
 676         $aHaveTables = $this->db()->getListOfTables();
 
 678         foreach ($aHaveTables as $sTable) {
 
 680             foreach ($aKeepTables as $sKeep) {
 
 681                 if (fnmatch($sKeep, $sTable)) {
 
 686             if (!$bFound) array_push($aDropTables, $sTable);
 
 688         foreach ($aDropTables as $sDrop) {
 
 689             $this->dropTable($sDrop);
 
 692         $this->removeFlatnodeFile();
 
 696      * Setup settings-frontend.php in the build/website directory
 
 700     public function setupWebsite()
 
 702         $rOutputFile = fopen(CONST_InstallPath.'/settings/settings-frontend.php', 'w');
 
 704         fwrite($rOutputFile, "<?php
 
 705 @define('CONST_BasePath', '".CONST_BasePath."');
 
 706 if (file_exists(getenv('NOMINATIM_SETTINGS'))) require_once(getenv('NOMINATIM_SETTINGS'));
 
 708 @define('CONST_Database_DSN', '".CONST_Database_DSN."'); // or add ;host=...;port=...;user=...;password=...
 
 709 @define('CONST_Default_Language', ".(CONST_Default_Language ? ("'".CONST_Default_Language."'") : 'false').");
 
 710 @define('CONST_Default_Lat', ".CONST_Default_Lat.");
 
 711 @define('CONST_Default_Lon', ".CONST_Default_Lon.");
 
 712 @define('CONST_Default_Zoom', ".CONST_Default_Zoom.");
 
 713 @define('CONST_Map_Tile_URL', '".CONST_Map_Tile_URL."');
 
 714 @define('CONST_Map_Tile_Attribution', '".CONST_Map_Tile_Attribution."'); // Set if tile source isn't osm.org
 
 715 @define('CONST_Log_DB', ".(CONST_Log_DB ? 'true' : 'false').");
 
 716 @define('CONST_Log_File', ".(CONST_Log_File ? ("'".CONST_Log_File."'")  : 'false').");
 
 717 @define('CONST_Max_Word_Frequency', '".CONST_Max_Word_Frequency."');
 
 718 @define('CONST_NoAccessControl', ".CONST_NoAccessControl.");
 
 719 @define('CONST_Places_Max_ID_count', ".CONST_Places_Max_ID_count.");
 
 720 @define('CONST_PolygonOutput_MaximumTypes', ".CONST_PolygonOutput_MaximumTypes.");
 
 721 @define('CONST_Search_AreaPolygons', ".CONST_Search_AreaPolygons.");
 
 722 @define('CONST_Search_BatchMode', ".(CONST_Search_BatchMode ? 'true' : 'false').");
 
 723 @define('CONST_Search_NameOnlySearchFrequencyThreshold', ".CONST_Search_NameOnlySearchFrequencyThreshold.");
 
 724 @define('CONST_Search_ReversePlanForAll', ".CONST_Search_ReversePlanForAll.");
 
 725 @define('CONST_Term_Normalization_Rules', \"".CONST_Term_Normalization_Rules."\");
 
 726 @define('CONST_Use_Aux_Location_data', ".(CONST_Use_Aux_Location_data ? 'true' : 'false').");
 
 727 @define('CONST_Use_US_Tiger_Data', ".(CONST_Use_US_Tiger_Data ? 'true' : 'false').");
 
 728 @define('CONST_Website_BaseURL', '".CONST_Website_BaseURL."');
 
 730         info(CONST_InstallPath.'/settings/settings-frontend.php has been set up successfully');
 
 734      * Return the connection to the database.
 
 736      * @return Database object.
 
 738      * Creates a new connection if none exists yet. Otherwise reuses the
 
 739      * already established connection.
 
 741     private function db()
 
 743         if (is_null($this->oDB)) {
 
 744             $this->oDB = new \Nominatim\DB();
 
 745             $this->oDB->connect();
 
 751     private function removeFlatnodeFile()
 
 753         if (!is_null(CONST_Osm2pgsql_Flatnode_File) && CONST_Osm2pgsql_Flatnode_File) {
 
 754             if (file_exists(CONST_Osm2pgsql_Flatnode_File)) {
 
 755                 if ($this->bVerbose) echo 'Deleting '.CONST_Osm2pgsql_Flatnode_File."\n";
 
 756                 unlink(CONST_Osm2pgsql_Flatnode_File);
 
 761     private function pgsqlRunScript($sScript, $bfatal = true)
 
 771     private function createSqlFunctions()
 
 773         $sBasePath = CONST_BasePath.'/sql/functions/';
 
 774         $sTemplate = file_get_contents($sBasePath.'utils.sql');
 
 775         $sTemplate .= file_get_contents($sBasePath.'normalization.sql');
 
 776         $sTemplate .= file_get_contents($sBasePath.'ranking.sql');
 
 777         $sTemplate .= file_get_contents($sBasePath.'importance.sql');
 
 778         $sTemplate .= file_get_contents($sBasePath.'address_lookup.sql');
 
 779         $sTemplate .= file_get_contents($sBasePath.'interpolation.sql');
 
 780         if ($this->db()->tableExists('place')) {
 
 781             $sTemplate .= file_get_contents($sBasePath.'place_triggers.sql');
 
 783         if ($this->db()->tableExists('placex')) {
 
 784             $sTemplate .= file_get_contents($sBasePath.'placex_triggers.sql');
 
 786         if ($this->db()->tableExists('location_postcode')) {
 
 787             $sTemplate .= file_get_contents($sBasePath.'postcode_triggers.sql');
 
 789         $sTemplate = str_replace('{modulepath}', $this->sModulePath, $sTemplate);
 
 790         if ($this->bEnableDiffUpdates) {
 
 791             $sTemplate = str_replace('RETURN NEW; -- %DIFFUPDATES%', '--', $sTemplate);
 
 793         if ($this->bEnableDebugStatements) {
 
 794             $sTemplate = str_replace('--DEBUG:', '', $sTemplate);
 
 796         if (CONST_Limit_Reindexing) {
 
 797             $sTemplate = str_replace('--LIMIT INDEXING:', '', $sTemplate);
 
 799         if (!CONST_Use_US_Tiger_Data) {
 
 800             $sTemplate = str_replace('-- %NOTIGERDATA% ', '', $sTemplate);
 
 802         if (!CONST_Use_Aux_Location_data) {
 
 803             $sTemplate = str_replace('-- %NOAUXDATA% ', '', $sTemplate);
 
 806         $sReverseOnly = $this->dbReverseOnly() ? 'true' : 'false';
 
 807         $sTemplate = str_replace('%REVERSE-ONLY%', $sReverseOnly, $sTemplate);
 
 809         $this->pgsqlRunScript($sTemplate);
 
 812     private function pgsqlRunPartitionScript($sTemplate)
 
 814         $sSQL = 'select distinct partition from country_name';
 
 815         $aPartitions = $this->db()->getCol($sSQL);
 
 816         if (!$this->bNoPartitions) $aPartitions[] = 0;
 
 818         preg_match_all('#^-- start(.*?)^-- end#ms', $sTemplate, $aMatches, PREG_SET_ORDER);
 
 819         foreach ($aMatches as $aMatch) {
 
 821             foreach ($aPartitions as $sPartitionName) {
 
 822                 $sResult .= str_replace('-partition-', $sPartitionName, $aMatch[1]);
 
 824             $sTemplate = str_replace($aMatch[0], $sResult, $sTemplate);
 
 827         $this->pgsqlRunScript($sTemplate);
 
 830     private function pgsqlRunScriptFile($sFilename)
 
 832         if (!file_exists($sFilename)) fail('unable to find '.$sFilename);
 
 834         $oCmd = (new \Nominatim\Shell('psql'))
 
 835                 ->addParams('--port', $this->aDSNInfo['port'])
 
 836                 ->addParams('--dbname', $this->aDSNInfo['database']);
 
 838         if (!$this->bVerbose) {
 
 839             $oCmd->addParams('--quiet');
 
 841         if (isset($this->aDSNInfo['hostspec'])) {
 
 842             $oCmd->addParams('--host', $this->aDSNInfo['hostspec']);
 
 844         if (isset($this->aDSNInfo['username'])) {
 
 845             $oCmd->addParams('--username', $this->aDSNInfo['username']);
 
 847         if (isset($this->aDSNInfo['password'])) {
 
 848             $oCmd->addEnvPair('PGPASSWORD', $this->aDSNInfo['password']);
 
 851         if (preg_match('/\\.gz$/', $sFilename)) {
 
 852             $aDescriptors = array(
 
 853                              0 => array('pipe', 'r'),
 
 854                              1 => array('pipe', 'w'),
 
 855                              2 => array('file', '/dev/null', 'a')
 
 857             $oZcatCmd = new \Nominatim\Shell('zcat', $sFilename);
 
 859             $hGzipProcess = proc_open($oZcatCmd->escapedCmd(), $aDescriptors, $ahGzipPipes);
 
 860             if (!is_resource($hGzipProcess)) fail('unable to start zcat');
 
 861             $aReadPipe = $ahGzipPipes[1];
 
 862             fclose($ahGzipPipes[0]);
 
 864             $oCmd->addParams('--file', $sFilename);
 
 865             $aReadPipe = array('pipe', 'r');
 
 867         $aDescriptors = array(
 
 869                          1 => array('pipe', 'w'),
 
 870                          2 => array('file', '/dev/null', 'a')
 
 874         $hProcess = proc_open($oCmd->escapedCmd(), $aDescriptors, $ahPipes, null, $oCmd->aEnv);
 
 875         if (!is_resource($hProcess)) fail('unable to start pgsql');
 
 876         // TODO: error checking
 
 877         while (!feof($ahPipes[1])) {
 
 878             echo fread($ahPipes[1], 4096);
 
 881         $iReturn = proc_close($hProcess);
 
 883             fail("pgsql returned with error code ($iReturn)");
 
 886             fclose($ahGzipPipes[1]);
 
 887             proc_close($hGzipProcess);
 
 891     private function replaceSqlPatterns($sSql)
 
 893         $sSql = str_replace('{www-user}', CONST_Database_Web_User, $sSql);
 
 896                       '{ts:address-data}' => CONST_Tablespace_Address_Data,
 
 897                       '{ts:address-index}' => CONST_Tablespace_Address_Index,
 
 898                       '{ts:search-data}' => CONST_Tablespace_Search_Data,
 
 899                       '{ts:search-index}' =>  CONST_Tablespace_Search_Index,
 
 900                       '{ts:aux-data}' =>  CONST_Tablespace_Aux_Data,
 
 901                       '{ts:aux-index}' =>  CONST_Tablespace_Aux_Index,
 
 904         foreach ($aPatterns as $sPattern => $sTablespace) {
 
 906                 $sSql = str_replace($sPattern, 'TABLESPACE "'.$sTablespace.'"', $sSql);
 
 908                 $sSql = str_replace($sPattern, '', $sSql);
 
 916      * Drop table with the given name if it exists.
 
 918      * @param string $sName Name of table to remove.
 
 922     private function dropTable($sName)
 
 924         if ($this->bVerbose) echo "Dropping table $sName\n";
 
 925         $this->db()->deleteTable($sName);
 
 929      * Check if the database is in reverse-only mode.
 
 931      * @return True if there is no search_name table and infrastructure.
 
 933     private function dbReverseOnly()
 
 935         return !($this->db()->tableExists('search_name'));