]> git.openstreetmap.org Git - nominatim.git/blob - lib/SetupClass.php
small fixes on setup.php and a bring update.php to work
[nominatim.git] / lib / SetupClass.php
1 <?php
2
3 namespace Nominatim\Setup;
4
5 class SetupFunctions
6 {
7     protected $iCacheMemory; // set in constructor
8     protected $iInstances; // set in constructor
9     protected $sModulePath; // set in constructor
10     protected $aDSNInfo; // set in constructor = DB::parseDSN(CONST_Database_DSN);
11     protected $sVerbose; // set in constructor
12     protected $sIgnoreErrors; // set in constructor
13     protected $bEnableDiffUpdates; // set in constructor
14     protected $bEnableDebugStatements; // set in constructor
15     protected $bNoPartitions; // set in constructor
16     protected $oDB = null; // set in setupDB (earliest) or later in loadData, importData, drop, createSqlFunctions, importTigerData
17     // pgsqlRunPartitionScript, calculatePostcodes, ..if no already set
18
19     public function __construct($aCMDResult)
20     {
21         // by default, use all but one processor, but never more than 15.
22         $this->iInstances = isset($aCMDResult['threads'])
23         ? $aCMDResult['threads']
24         : (min(16, getProcessorCount()) - 1);
25
26         if ($this->iInstances < 1) {
27             $this->iInstances = 1;
28             warn('resetting threads to ' . $this->iInstances);
29         }
30
31         // Assume we can steal all the cache memory in the box (unless told otherwise)
32         if (isset($aCMDResult['osm2pgsql-cache'])) {
33             $this->iCacheMemory = $aCMDResult['osm2pgsql-cache'];
34         } else {
35             $this->iCacheMemory = getCacheMemoryMB();
36         }
37
38         $this->sModulePath = CONST_Database_Module_Path;
39         info('module path: ' . $this->sModulePath);
40
41         // prepares DB for import or update, sets the Data Source Name
42         $this->aDSNInfo = \DB::parseDSN(CONST_Database_DSN);
43         if (!isset($this->aDSNInfo['port']) || !$this->aDSNInfo['port']) {
44             $this->aDSNInfo['port'] = 5432;
45         }
46
47         // setting member variables based on command line options stored in $aCMDResult
48         $this->sVerbose = $aCMDResult['verbose'];
49         $this->sIgnoreErrors = $aCMDResult['ignore-errors'];
50         $this->bEnableDiffUpdates = $aCMDResult['enable-diff-updates'];
51         $this->bEnableDebugStatements = $aCMDResult['enable-debug-statements'];
52         $this->bNoPartitions = $aCMDResult['no-partitions'];
53     }
54
55     public function createDB()
56     {
57         info('Create DB');
58         $sDB = \DB::connect(CONST_Database_DSN, false);
59         if (!\PEAR::isError($sDB)) {
60             fail('database already exists (' . CONST_Database_DSN . ')');
61         }
62
63         $sCreateDBCmd = 'createdb -E UTF-8 -p ' . $this->aDSNInfo['port'] . ' ' . $this->aDSNInfo['database'];
64         if (isset($this->aDSNInfo['username']) && $this->aDSNInfo['username']) {
65             $sCreateDBCmd .= ' -U ' . $this->aDSNInfo['username'];
66         }
67
68         if (isset($this->aDSNInfo['hostspec']) && $this->aDSNInfo['hostspec']) {
69             $sCreateDBCmd .= ' -h ' . $this->aDSNInfo['hostspec'];
70         }
71
72         $aProcEnv = null;
73         if (isset($this->aDSNInfo['password']) && $this->aDSNInfo['password']) {
74             $aProcEnv = array_merge(array('PGPASSWORD' => $this->aDSNInfo['password']), $_ENV);
75         }
76
77         $result = runWithEnv($sCreateDBCmd, $aProcEnv);
78         if ($result != 0) {
79             fail('Error executing external command: ' . $sCreateDBCmd);
80         }
81
82     }
83
84     public function setupDB()
85     {
86         info('Setup DB');
87         $this->oDB = &getDB();
88
89         $fPostgresVersion = getPostgresVersion($this->oDB);
90         echo 'Postgres version found: ' . $fPostgresVersion . "\n";
91
92         if ($fPostgresVersion < 9.1) {
93             fail('Minimum supported version of Postgresql is 9.1.');
94         }
95
96         $this->pgsqlRunScript('CREATE EXTENSION IF NOT EXISTS hstore');
97         $this->pgsqlRunScript('CREATE EXTENSION IF NOT EXISTS postgis');
98
99         // For extratags and namedetails the hstore_to_json converter is
100         // needed which is only available from Postgresql 9.3+. For older
101         // versions add a dummy function that returns nothing.
102         $iNumFunc = chksql($this->oDB->getOne("select count(*) from pg_proc where proname = 'hstore_to_json'"));
103
104         if ($iNumFunc == 0) {
105             $this->pgsqlRunScript("create function hstore_to_json(dummy hstore) returns text AS 'select null::text' language sql immutable");
106             warn('Postgresql is too old. extratags and namedetails API not available.');
107         }
108
109         $fPostgisVersion = getPostgisVersion($this->oDB);
110         echo 'Postgis version found: ' . $fPostgisVersion . "\n";
111
112         if ($fPostgisVersion < 2.1) {
113             // Functions were renamed in 2.1 and throw an annoying deprecation warning
114             $this->pgsqlRunScript('ALTER FUNCTION st_line_interpolate_point(geometry, double precision) RENAME TO ST_LineInterpolatePoint');
115             $this->pgsqlRunScript('ALTER FUNCTION ST_Line_Locate_Point(geometry, geometry) RENAME TO ST_LineLocatePoint');
116         }
117         if ($fPostgisVersion < 2.2) {
118             $this->pgsqlRunScript('ALTER FUNCTION ST_Distance_Spheroid(geometry, geometry, spheroid) RENAME TO ST_DistanceSpheroid');
119         }
120
121         $i = chksql($this->oDB->getOne("select count(*) from pg_user where usename = '" . CONST_Database_Web_User . "'"));
122         if ($i == 0) {
123             echo "\nERROR: Web user '" . CONST_Database_Web_User . "' does not exist. Create it with:\n";
124             echo "\n          createuser " . CONST_Database_Web_User . "\n\n";
125             exit(1);
126         }
127
128         if (!file_exists(CONST_ExtraDataPath . '/country_osm_grid.sql.gz')) {
129             echo 'Error: you need to download the country_osm_grid first:';
130             echo "\n    wget -O " . CONST_ExtraDataPath . "/country_osm_grid.sql.gz https://www.nominatim.org/data/country_grid.sql.gz\n";
131             exit(1);
132         }
133         $this->pgsqlRunScriptFile(CONST_BasePath . '/data/country_name.sql');
134         $this->pgsqlRunScriptFile(CONST_BasePath . '/data/country_naturalearthdata.sql');
135         $this->pgsqlRunScriptFile(CONST_BasePath . '/data/country_osm_grid.sql.gz');
136         $this->pgsqlRunScriptFile(CONST_BasePath . '/data/gb_postcode_table.sql');
137
138         if (file_exists(CONST_BasePath . '/data/gb_postcode_data.sql.gz')) {
139             $this->pgsqlRunScriptFile(CONST_BasePath . '/data/gb_postcode_data.sql.gz');
140         } else {
141             warn('external UK postcode table not found.');
142         }
143
144         if (CONST_Use_Extra_US_Postcodes) {
145             $this->pgsqlRunScriptFile(CONST_BasePath . '/data/us_postcode.sql');
146         }
147
148         if ($this->bNoPartitions) {
149             $this->pgsqlRunScript('update country_name set partition = 0');
150         }
151
152         // the following will be needed by create_functions later but
153         // is only defined in the subsequently called T
154         // Create dummies here that will be overwritten by the proper
155         // versions in create-tables.
156         $this->pgsqlRunScript('CREATE TABLE IF NOT EXISTS place_boundingbox ()');
157         $this->pgsqlRunScript('CREATE TYPE wikipedia_article_match AS ()', false);
158     }
159
160     public function importData($sOSMFile)
161     {
162         info('Import data');
163
164         $osm2pgsql = CONST_Osm2pgsql_Binary;
165         if (!file_exists($osm2pgsql)) {
166             echo "Check CONST_Osm2pgsql_Binary in your local settings file.\n";
167             echo "Normally you should not need to set this manually.\n";
168             fail("osm2pgsql not found in '$osm2pgsql'");
169         }
170
171         if (!is_null(CONST_Osm2pgsql_Flatnode_File) && CONST_Osm2pgsql_Flatnode_File) {
172             $osm2pgsql .= ' --flat-nodes ' . CONST_Osm2pgsql_Flatnode_File;
173         }
174
175         if (CONST_Tablespace_Osm2pgsql_Data) {
176             $osm2pgsql .= ' --tablespace-slim-data ' . CONST_Tablespace_Osm2pgsql_Data;
177         }
178
179         if (CONST_Tablespace_Osm2pgsql_Index) {
180             $osm2pgsql .= ' --tablespace-slim-index ' . CONST_Tablespace_Osm2pgsql_Index;
181         }
182
183         if (CONST_Tablespace_Place_Data) {
184             $osm2pgsql .= ' --tablespace-main-data ' . CONST_Tablespace_Place_Data;
185         }
186
187         if (CONST_Tablespace_Place_Index) {
188             $osm2pgsql .= ' --tablespace-main-index ' . CONST_Tablespace_Place_Index;
189         }
190
191         $osm2pgsql .= ' -lsc -O gazetteer --hstore --number-processes 1';
192         $osm2pgsql .= ' -C ' . $this->iCacheMemory;
193         $osm2pgsql .= ' -P ' . $this->aDSNInfo['port'];
194         if (isset($this->aDSNInfo['username']) && $this->aDSNInfo['username']) {
195             $osm2pgsql .= ' -U ' . $this->aDSNInfo['username'];
196         }
197         if (isset($this->aDSNInfo['hostspec']) && $this->aDSNInfo['hostspec']) {
198             $osm2pgsql .= ' -H ' . $this->aDSNInfo['hostspec'];
199         }
200         $aProcEnv = null;
201         if (isset($this->aDSNInfo['password']) && $this->aDSNInfo['password']) {
202             $aProcEnv = array_merge(array('PGPASSWORD' => $this->aDSNInfo['password']), $_ENV);
203         }
204         $osm2pgsql .= ' -d ' . $this->aDSNInfo['database'] . ' ' . $sOSMFile;
205         runWithEnv($osm2pgsql, $aProcEnv);
206         if ($this->oDB == null) {
207             $this->oDB = &getDB();
208         }
209
210         if (!$this->sIgnoreErrors && !chksql($this->oDB->getRow('select * from place limit 1'))) {
211             fail('No Data');
212         }
213     }
214
215     public function createFunctions()
216     {
217         info('Create Functions');
218
219         $this->createSqlFunctions();
220     }
221
222     public function createTables()
223     {
224         info('Create Tables');
225
226         $sTemplate = file_get_contents(CONST_BasePath . '/sql/tables.sql');
227         $sTemplate = str_replace('{www-user}', CONST_Database_Web_User, $sTemplate);
228         $sTemplate = $this->replaceTablespace(
229             '{ts:address-data}',
230             CONST_Tablespace_Address_Data,
231             $sTemplate
232         );
233         $sTemplate = $this->replaceTablespace(
234             '{ts:address-index}',
235             CONST_Tablespace_Address_Index,
236             $sTemplate
237         );
238         $sTemplate = $this->replaceTablespace(
239             '{ts:search-data}',
240             CONST_Tablespace_Search_Data,
241             $sTemplate
242         );
243         $sTemplate = $this->replaceTablespace(
244             '{ts:search-index}',
245             CONST_Tablespace_Search_Index,
246             $sTemplate
247         );
248         $sTemplate = $this->replaceTablespace(
249             '{ts:aux-data}',
250             CONST_Tablespace_Aux_Data,
251             $sTemplate
252         );
253         $sTemplate = $this->replaceTablespace(
254             '{ts:aux-index}',
255             CONST_Tablespace_Aux_Index,
256             $sTemplate
257         );
258
259         $this->pgsqlRunScript($sTemplate, false);
260     }
261
262     public function createPartitionTables()
263     {
264         info('Create Partition Tables');
265
266         $sTemplate = file_get_contents(CONST_BasePath . '/sql/partition-tables.src.sql');
267         $sTemplate = $this->replaceTablespace(
268             '{ts:address-data}',
269             CONST_Tablespace_Address_Data,
270             $sTemplate
271         );
272
273         $sTemplate = $this->replaceTablespace(
274             '{ts:address-index}',
275             CONST_Tablespace_Address_Index,
276             $sTemplate
277         );
278
279         $sTemplate = $this->replaceTablespace(
280             '{ts:search-data}',
281             CONST_Tablespace_Search_Data,
282             $sTemplate
283         );
284
285         $sTemplate = $this->replaceTablespace(
286             '{ts:search-index}',
287             CONST_Tablespace_Search_Index,
288             $sTemplate
289         );
290
291         $sTemplate = $this->replaceTablespace(
292             '{ts:aux-data}',
293             CONST_Tablespace_Aux_Data,
294             $sTemplate
295         );
296
297         $sTemplate = $this->replaceTablespace(
298             '{ts:aux-index}',
299             CONST_Tablespace_Aux_Index,
300             $sTemplate
301         );
302
303         $this->pgsqlRunPartitionScript($sTemplate);
304     }
305
306     public function createPartitionFunctions()
307     {
308         info('Create Partition Functions');
309
310         $sTemplate = file_get_contents(CONST_BasePath . '/sql/partition-functions.src.sql');
311         $this->pgsqlRunPartitionScript($sTemplate);
312     }
313
314     public function importWikipediaArticles()
315     {
316         $sWikiArticlesFile = CONST_Wikipedia_Data_Path . '/wikipedia_article.sql.bin';
317         $sWikiRedirectsFile = CONST_Wikipedia_Data_Path . '/wikipedia_redirect.sql.bin';
318         if (file_exists($sWikiArticlesFile)) {
319             info('Importing wikipedia articles');
320             $this->pgsqlRunDropAndRestore($sWikiArticlesFile);
321         } else {
322             warn('wikipedia article dump file not found - places will have default importance');
323         }
324         if (file_exists($sWikiRedirectsFile)) {
325             info('Importing wikipedia redirects');
326             $this->pgsqlRunDropAndRestore($sWikiRedirectsFile);
327         } else {
328             warn('wikipedia redirect dump file not found - some place importance values may be missing');
329         }
330         echo ' finish wikipedia';
331     }
332
333     public function loadData($bDisableTokenPrecalc)
334     {
335         info('Drop old Data');
336
337         if ($this->oDB == null) {
338             $this->oDB = &getDB();
339         }
340
341         if (!pg_query($this->oDB->connection, 'TRUNCATE word')) {
342             fail(pg_last_error($this->oDB->connection));
343         }
344
345         echo '.';
346         if (!pg_query($this->oDB->connection, 'TRUNCATE placex')) {
347             fail(pg_last_error($this->oDB->connection));
348         }
349
350         echo '.';
351         if (!pg_query($this->oDB->connection, 'TRUNCATE location_property_osmline')) {
352             fail(pg_last_error($this->oDB->connection));
353         }
354
355         echo '.';
356         if (!pg_query($this->oDB->connection, 'TRUNCATE place_addressline')) {
357             fail(pg_last_error($this->oDB->connection));
358         }
359
360         echo '.';
361         if (!pg_query($this->oDB->connection, 'TRUNCATE place_boundingbox')) {
362             fail(pg_last_error($this->oDB->connection));
363         }
364
365         echo '.';
366         if (!pg_query($this->oDB->connection, 'TRUNCATE location_area')) {
367             fail(pg_last_error($this->oDB->connection));
368         }
369
370         echo '.';
371         if (!pg_query($this->oDB->connection, 'TRUNCATE search_name')) {
372             fail(pg_last_error($this->oDB->connection));
373         }
374
375         echo '.';
376         if (!pg_query($this->oDB->connection, 'TRUNCATE search_name_blank')) {
377             fail(pg_last_error($this->oDB->connection));
378         }
379
380         echo '.';
381         if (!pg_query($this->oDB->connection, 'DROP SEQUENCE seq_place')) {
382             fail(pg_last_error($this->oDB->connection));
383         }
384
385         echo '.';
386         if (!pg_query($this->oDB->connection, 'CREATE SEQUENCE seq_place start 100000')) {
387             fail(pg_last_error($this->oDB->connection));
388         }
389
390         echo '.';
391
392         $sSQL = 'select distinct partition from country_name';
393         $aPartitions = chksql($this->oDB->getCol($sSQL));
394         if (!$this->bNoPartitions) {
395             $aPartitions[] = 0;
396         }
397
398         foreach ($aPartitions as $sPartition) {
399             if (!pg_query($this->oDB->connection, 'TRUNCATE location_road_' . $sPartition)) {
400                 fail(pg_last_error($this->oDB->connection));
401             }
402
403             echo '.';
404         }
405
406         // used by getorcreate_word_id to ignore frequent partial words
407         $sSQL = 'CREATE OR REPLACE FUNCTION get_maxwordfreq() RETURNS integer AS ';
408         $sSQL .= '$$ SELECT ' . CONST_Max_Word_Frequency . ' as maxwordfreq; $$ LANGUAGE SQL IMMUTABLE';
409         if (!pg_query($this->oDB->connection, $sSQL)) {
410             fail(pg_last_error($this->oDB->connection));
411         }
412         echo ".\n";
413
414         // pre-create the word list
415         if (!$bDisableTokenPrecalc) {
416             info('Loading word list');
417             $this->pgsqlRunScriptFile(CONST_BasePath . '/data/words.sql');
418         }
419
420         info('Load Data');
421         $sColumns = 'osm_type, osm_id, class, type, name, admin_level, address, extratags, geometry';
422         $aDBInstances = array();
423         $iLoadThreads = max(1, $this->iInstances - 1);
424         for ($i = 0; $i < $iLoadThreads; $i++) {
425             $aDBInstances[$i] = &getDB(true);
426             $sSQL = "INSERT INTO placex ($sColumns) SELECT $sColumns FROM place WHERE osm_id % $iLoadThreads = $i";
427             $sSQL .= " and not (class='place' and type='houses' and osm_type='W'";
428             $sSQL .= "          and ST_GeometryType(geometry) = 'ST_LineString')";
429             $sSQL .= ' and ST_IsValid(geometry)';
430             if ($this->sVerbose) {
431                 echo "$sSQL\n";
432             }
433
434             if (!pg_send_query($aDBInstances[$i]->connection, $sSQL)) {
435                 fail(pg_last_error($aDBInstances[$i]->connection));
436             }
437         }
438
439         // last thread for interpolation lines
440         $aDBInstances[$iLoadThreads] = &getDB(true);
441         $sSQL = 'insert into location_property_osmline';
442         $sSQL .= ' (osm_id, address, linegeo)';
443         $sSQL .= ' SELECT osm_id, address, geometry from place where ';
444         $sSQL .= "class='place' and type='houses' and osm_type='W' and ST_GeometryType(geometry) = 'ST_LineString'";
445         if ($this->sVerbose) {
446             echo "$sSQL\n";
447         }
448
449         if (!pg_send_query($aDBInstances[$iLoadThreads]->connection, $sSQL)) {
450             fail(pg_last_error($aDBInstances[$iLoadThreads]->connection));
451         }
452
453         $bFailed = false;
454         for ($i = 0; $i <= $iLoadThreads; $i++) {
455             while (($hPGresult = pg_get_result($aDBInstances[$i]->connection)) !== false) {
456                 $resultStatus = pg_result_status($hPGresult);
457                 // PGSQL_EMPTY_QUERY, PGSQL_COMMAND_OK, PGSQL_TUPLES_OK,
458                 // PGSQL_COPY_OUT, PGSQL_COPY_IN, PGSQL_BAD_RESPONSE,
459                 // PGSQL_NONFATAL_ERROR and PGSQL_FATAL_ERROR
460                 echo 'Query result ' . $i . ' is: ' . $resultStatus . "\n";
461                 if ($resultStatus != PGSQL_COMMAND_OK && $resultStatus != PGSQL_TUPLES_OK) {
462                     $resultError = pg_result_error($hPGresult);
463                     echo '-- error text ' . $i . ': ' . $resultError . "\n";
464                     $bFailed = true;
465                 }
466             }
467         }
468         if ($bFailed) {
469             fail('SQL errors loading placex and/or location_property_osmline tables');
470         }
471         echo "\n";
472         info('Reanalysing database');
473         $this->pgsqlRunScript('ANALYSE');
474
475         $sDatabaseDate = getDatabaseDate($this->oDB);
476         pg_query($this->oDB->connection, 'TRUNCATE import_status');
477         if ($sDatabaseDate === false) {
478             warn('could not determine database date.');
479         } else {
480             $sSQL = "INSERT INTO import_status (lastimportdate) VALUES('" . $sDatabaseDate . "')";
481             pg_query($this->oDB->connection, $sSQL);
482             echo "Latest data imported from $sDatabaseDate.\n";
483         }
484     }
485
486     public function importTigerData()
487     {
488         info('Import Tiger data');
489
490         $sTemplate = file_get_contents(CONST_BasePath . '/sql/tiger_import_start.sql');
491         $sTemplate = str_replace('{www-user}', CONST_Database_Web_User, $sTemplate);
492         $sTemplate = $this->replaceTablespace(
493             '{ts:aux-data}',
494             CONST_Tablespace_Aux_Data,
495             $sTemplate
496         );
497         $sTemplate = $this->replaceTablespace(
498             '{ts:aux-index}',
499             CONST_Tablespace_Aux_Index,
500             $sTemplate
501         );
502         $this->pgsqlRunScript($sTemplate, false);
503
504         $aDBInstances = array();
505         for ($i = 0; $i < $this->iInstances; $i++) {
506             $aDBInstances[$i] = &getDB(true);
507         }
508
509         foreach (glob(CONST_Tiger_Data_Path . '/*.sql') as $sFile) {
510             echo $sFile . ': ';
511             $hFile = fopen($sFile, 'r');
512             $sSQL = fgets($hFile, 100000);
513             $iLines = 0;
514             while (true) {
515                 for ($i = 0; $i < $this->iInstances; $i++) {
516                     if (!pg_connection_busy($aDBInstances[$i]->connection)) {
517                         while (pg_get_result($aDBInstances[$i]->connection));
518                         $sSQL = fgets($hFile, 100000);
519                         if (!$sSQL) {
520                             break 2;
521                         }
522
523                         if (!pg_send_query($aDBInstances[$i]->connection, $sSQL)) {
524                             fail(pg_last_error($this->oDB->connection));
525                         }
526
527                         $iLines++;
528                         if ($iLines == 1000) {
529                             echo '.';
530                             $iLines = 0;
531                         }
532                     }
533                 }
534                 usleep(10);
535             }
536             fclose($hFile);
537
538             $bAnyBusy = true;
539             while ($bAnyBusy) {
540                 $bAnyBusy = false;
541                 for ($i = 0; $i < $this->iInstances; $i++) {
542                     if (pg_connection_busy($aDBInstances[$i]->connection)) {
543                         $bAnyBusy = true;
544                     }
545
546                 }
547                 usleep(10);
548             }
549             echo "\n";
550         }
551
552         info('Creating indexes on Tiger data');
553         $sTemplate = file_get_contents(CONST_BasePath . '/sql/tiger_import_finish.sql');
554         $sTemplate = str_replace('{www-user}', CONST_Database_Web_User, $sTemplate);
555         $sTemplate = $this->replaceTablespace(
556             '{ts:aux-data}',
557             CONST_Tablespace_Aux_Data,
558             $sTemplate
559         );
560         $sTemplate = $this->replaceTablespace(
561             '{ts:aux-index}',
562             CONST_Tablespace_Aux_Index,
563             $sTemplate
564         );
565         $this->pgsqlRunScript($sTemplate, false);
566     }
567
568     public function calculatePostcodes($bCMDResultAll)
569     {
570         info('Calculate Postcodes');
571         if ($this->oDB == null) {
572             $this->oDB = &getDB();
573         }
574
575         if (!pg_query($this->oDB->connection, 'TRUNCATE location_postcode')) {
576             fail(pg_last_error($this->oDB->connection));
577         }
578
579         $sSQL = 'INSERT INTO location_postcode';
580         $sSQL .= ' (place_id, indexed_status, country_code, postcode, geometry) ';
581         $sSQL .= "SELECT nextval('seq_place'), 1, country_code,";
582         $sSQL .= "       upper(trim (both ' ' from address->'postcode')) as pc,";
583         $sSQL .= '       ST_Centroid(ST_Collect(ST_Centroid(geometry)))';
584         $sSQL .= '  FROM placex';
585         $sSQL .= " WHERE address ? 'postcode' AND address->'postcode' NOT SIMILAR TO '%(,|;)%'";
586         $sSQL .= '       AND geometry IS NOT null';
587         $sSQL .= ' GROUP BY country_code, pc';
588
589         if (!pg_query($this->oDB->connection, $sSQL)) {
590             fail(pg_last_error($this->oDB->connection));
591         }
592
593         if (CONST_Use_Extra_US_Postcodes) {
594             // only add postcodes that are not yet available in OSM
595             $sSQL = 'INSERT INTO location_postcode';
596             $sSQL .= ' (place_id, indexed_status, country_code, postcode, geometry) ';
597             $sSQL .= "SELECT nextval('seq_place'), 1, 'us', postcode,";
598             $sSQL .= '       ST_SetSRID(ST_Point(x,y),4326)';
599             $sSQL .= '  FROM us_postcode WHERE postcode NOT IN';
600             $sSQL .= '        (SELECT postcode FROM location_postcode';
601             $sSQL .= "          WHERE country_code = 'us')";
602             if (!pg_query($this->oDB->connection, $sSQL)) {
603                 fail(pg_last_error($this->oDB->connection));
604             }
605
606         }
607
608         // add missing postcodes for GB (if available)
609         $sSQL = 'INSERT INTO location_postcode';
610         $sSQL .= ' (place_id, indexed_status, country_code, postcode, geometry) ';
611         $sSQL .= "SELECT nextval('seq_place'), 1, 'gb', postcode, geometry";
612         $sSQL .= '  FROM gb_postcode WHERE postcode NOT IN';
613         $sSQL .= '           (SELECT postcode FROM location_postcode';
614         $sSQL .= "             WHERE country_code = 'gb')";
615         if (!pg_query($this->oDB->connection, $sSQL)) {
616             fail(pg_last_error($this->oDB->connection));
617         }
618
619         if (!$bCMDResultAll) {
620             $sSQL = "DELETE FROM word WHERE class='place' and type='postcode'";
621             $sSQL .= 'and word NOT IN (SELECT postcode FROM location_postcode)';
622             if (!pg_query($this->oDB->connection, $sSQL)) {
623                 fail(pg_last_error($this->oDB->connection));
624             }
625         }
626         $sSQL = 'SELECT count(getorcreate_postcode_id(v)) FROM ';
627         $sSQL .= '(SELECT distinct(postcode) as v FROM location_postcode) p';
628
629         if (!pg_query($this->oDB->connection, $sSQL)) {
630             fail(pg_last_error($this->oDB->connection));
631         }
632     }
633
634     public function index($bIndexNoanalyse)
635     {
636         $sOutputFile = '';
637         $sBaseCmd = CONST_InstallPath . '/nominatim/nominatim -i -d ' . $this->aDSNInfo['database'] . ' -P '
638         . $this->aDSNInfo['port'] . ' -t ' . $this->iInstances . $sOutputFile;
639         if (isset($this->aDSNInfo['hostspec']) && $this->aDSNInfo['hostspec']) {
640             $sBaseCmd .= ' -H ' . $this->aDSNInfo['hostspec'];
641         }
642         if (isset($this->aDSNInfo['username']) && $this->aDSNInfo['username']) {
643             $sBaseCmd .= ' -U ' . $this->aDSNInfo['username'];
644         }
645         $aProcEnv = null;
646         if (isset($this->aDSNInfo['password']) && $this->aDSNInfo['password']) {
647             $aProcEnv = array_merge(array('PGPASSWORD' => $this->aDSNInfo['password']), $_ENV);
648         }
649
650         info('Index ranks 0 - 4');
651         $iStatus = runWithEnv($sBaseCmd . ' -R 4', $aProcEnv);
652         if ($iStatus != 0) {
653             fail('error status ' . $iStatus . ' running nominatim!');
654         }
655         if (!$bIndexNoanalyse) {
656             $this->pgsqlRunScript('ANALYSE');
657         }
658
659         info('Index ranks 5 - 25');
660         $iStatus = runWithEnv($sBaseCmd . ' -r 5 -R 25', $aProcEnv);
661         if ($iStatus != 0) {
662             fail('error status ' . $iStatus . ' running nominatim!');
663         }
664         if (!$bIndexNoanalyse) {
665             $this->pgsqlRunScript('ANALYSE');
666         }
667
668         info('Index ranks 26 - 30');
669         $iStatus = runWithEnv($sBaseCmd . ' -r 26', $aProcEnv);
670         if ($iStatus != 0) {
671             fail('error status ' . $iStatus . ' running nominatim!');
672         }
673         info('Index postcodes');
674         if ($this->oDB == null) {
675             $this->oDB = &getDB();
676         }
677
678         $sSQL = 'UPDATE location_postcode SET indexed_status = 0';
679         if (!pg_query($this->oDB->connection, $sSQL)) {
680             fail(pg_last_error($this->oDB->connection));
681         }
682
683     }
684
685     public function createSearchIndices()
686     {
687         info('Create Search indices');
688
689         $sTemplate = file_get_contents(CONST_BasePath . '/sql/indices.src.sql');
690         $sTemplate = str_replace('{www-user}', CONST_Database_Web_User, $sTemplate);
691         $sTemplate = $this->replaceTablespace(
692             '{ts:address-index}',
693             CONST_Tablespace_Address_Index,
694             $sTemplate
695         );
696         $sTemplate = $this->replaceTablespace(
697             '{ts:search-index}',
698             CONST_Tablespace_Search_Index,
699             $sTemplate
700         );
701         $sTemplate = $this->replaceTablespace(
702             '{ts:aux-index}',
703             CONST_Tablespace_Aux_Index,
704             $sTemplate
705         );
706         $this->pgsqlRunScript($sTemplate);
707     }
708
709     public function createCountryNames()
710     {
711         info('Create search index for default country names');
712
713         $this->pgsqlRunScript("select getorcreate_country(make_standard_name('uk'), 'gb')");
714         $this->pgsqlRunScript("select getorcreate_country(make_standard_name('united states'), 'us')");
715         $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');
716         $this->pgsqlRunScript("select count(*) from (select getorcreate_country(make_standard_name(name->'name'), country_code) from country_name where name ? 'name') as x");
717         $sSQL = 'select count(*) from (select getorcreate_country(make_standard_name(v),'
718             . 'country_code) from (select country_code, skeys(name) as k, svals(name) as v from country_name) x where k ';
719         if (CONST_Languages) {
720             $sSQL .= 'in ';
721             $sDelim = '(';
722             foreach (explode(',', CONST_Languages) as $sLang) {
723                 $sSQL .= $sDelim . "'name:$sLang'";
724                 $sDelim = ',';
725             }
726             $sSQL .= ')';
727         } else {
728             // all include all simple name tags
729             $sSQL .= "like 'name:%'";
730         }
731         $sSQL .= ') v';
732         $this->pgsqlRunScript($sSQL);
733     }
734
735     public function drop()
736     {
737         info('Drop tables only required for updates');
738
739         // The implementation is potentially a bit dangerous because it uses
740         // a positive selection of tables to keep, and deletes everything else.
741         // Including any tables that the unsuspecting user might have manually
742         // created. USE AT YOUR OWN PERIL.
743         // tables we want to keep. everything else goes.
744         $aKeepTables = array(
745             '*columns',
746             'import_polygon_*',
747             'import_status',
748             'place_addressline',
749             'location_postcode',
750             'location_property*',
751             'placex',
752             'search_name',
753             'seq_*',
754             'word',
755             'query_log',
756             'new_query_log',
757             'spatial_ref_sys',
758             'country_name',
759             'place_classtype_*',
760         );
761
762         if ($this->oDB = null) {
763             $this->oDB = &getDB();
764         }
765
766         $aDropTables = array();
767         $aHaveTables = chksql($this->oDB->getCol("SELECT tablename FROM pg_tables WHERE schemaname='public'"));
768
769         foreach ($aHaveTables as $sTable) {
770             $bFound = false;
771             foreach ($aKeepTables as $sKeep) {
772                 if (fnmatch($sKeep, $sTable)) {
773                     $bFound = true;
774                     break;
775                 }
776             }
777             if (!$bFound) {
778                 array_push($aDropTables, $sTable);
779             }
780
781         }
782         foreach ($aDropTables as $sDrop) {
783             if ($this->sVerbose) {
784                 echo "dropping table $sDrop\n";
785             }
786
787             @pg_query($this->oDB->connection, "DROP TABLE $sDrop CASCADE");
788             // ignore warnings/errors as they might be caused by a table having
789             // been deleted already by CASCADE
790         }
791
792         if (!is_null(CONST_Osm2pgsql_Flatnode_File) && CONST_Osm2pgsql_Flatnode_File) {
793             if ($sVerbose) {
794                 echo 'deleting ' . CONST_Osm2pgsql_Flatnode_File . "\n";
795             }
796
797             unlink(CONST_Osm2pgsql_Flatnode_File);
798         }
799     }
800
801     private function pgsqlRunDropAndRestore($sDumpFile)
802     {
803         if (!isset($this->aDSNInfo['port']) || !$this->aDSNInfo['port']) {
804             $this->aDSNInfo['port'] = 5432;
805         }
806
807         $sCMD = 'pg_restore -p ' . $this->aDSNInfo['port'] . ' -d ' . $this->aDSNInfo['database'] . ' -Fc --clean ' . $sDumpFile;
808         if (isset($this->aDSNInfo['hostspec']) && $this->aDSNInfo['hostspec']) {
809             $sCMD .= ' -h ' . $this->aDSNInfo['hostspec'];
810         }
811         if (isset($this->aDSNInfo['username']) && $this->aDSNInfo['username']) {
812             $sCMD .= ' -U ' . $this->aDSNInfo['username'];
813         }
814         $aProcEnv = null;
815         if (isset($this->aDSNInfo['password']) && $this->aDSNInfo['password']) {
816             $aProcEnv = array_merge(array('PGPASSWORD' => $this->aDSNInfo['password']), $_ENV);
817         }
818         $iReturn = runWithEnv($sCMD, $aProcEnv); // /lib/cmd.php "function runWithEnv($sCmd, $aEnv)"
819     }
820
821     private function pgsqlRunScript($sScript, $bfatal = true)
822     {
823         runSQLScript(
824             $sScript,
825             $bfatal,
826             $this->sVerbose,
827             $this->sIgnoreErrors
828         );
829     }
830
831     private function createSqlFunctions()
832     {
833         $sTemplate = file_get_contents(CONST_BasePath . '/sql/functions.sql');
834         $sTemplate = str_replace('{modulepath}', $this->sModulePath, $sTemplate);
835         if ($this->bEnableDiffUpdates) {
836             $sTemplate = str_replace('RETURN NEW; -- %DIFFUPDATES%', '--', $sTemplate);
837         }
838         if ($this->bEnableDebugStatements) {
839             $sTemplate = str_replace('--DEBUG:', '', $sTemplate);
840         }
841         if (CONST_Limit_Reindexing) {
842             $sTemplate = str_replace('--LIMIT INDEXING:', '', $sTemplate);
843         }
844         if (!CONST_Use_US_Tiger_Data) {
845             $sTemplate = str_replace('-- %NOTIGERDATA% ', '', $sTemplate);
846         }
847         if (!CONST_Use_Aux_Location_data) {
848             $sTemplate = str_replace('-- %NOAUXDATA% ', '', $sTemplate);
849         }
850         $this->pgsqlRunScript($sTemplate);
851     }
852
853     private function pgsqlRunPartitionScript($sTemplate)
854     {
855         if ($this->oDB == null) {
856             $this->oDB = &getDB();
857         }
858
859         $sSQL = 'select distinct partition from country_name';
860         $aPartitions = chksql($this->oDB->getCol($sSQL));
861         if (!$this->bNoPartitions) {
862             $aPartitions[] = 0;
863         }
864
865         preg_match_all('#^-- start(.*?)^-- end#ms', $sTemplate, $aMatches, PREG_SET_ORDER);
866         foreach ($aMatches as $aMatch) {
867             $sResult = '';
868             foreach ($aPartitions as $sPartitionName) {
869                 $sResult .= str_replace('-partition-', $sPartitionName, $aMatch[1]);
870             }
871             $sTemplate = str_replace($aMatch[0], $sResult, $sTemplate);
872         }
873
874         $this->pgsqlRunScript($sTemplate);
875     }
876
877     private function pgsqlRunScriptFile($sFilename)
878     {
879         if (!file_exists($sFilename)) {
880             fail('unable to find ' . $sFilename);
881         }
882
883         $sCMD = 'psql -p ' . $this->aDSNInfo['port'] . ' -d ' . $this->aDSNInfo['database'];
884         if (!$this->sVerbose) {
885             $sCMD .= ' -q';
886         }
887         if (isset($this->aDSNInfo['hostspec']) && $this->aDSNInfo['hostspec']) {
888             $sCMD .= ' -h ' . $this->aDSNInfo['hostspec'];
889         }
890         if (isset($this->aDSNInfo['username']) && $this->aDSNInfo['username']) {
891             $sCMD .= ' -U ' . $this->aDSNInfo['username'];
892         }
893         $aProcEnv = null;
894         if (isset($this->aDSNInfo['password']) && $this->aDSNInfo['password']) {
895             $aProcEnv = array_merge(array('PGPASSWORD' => $this->aDSNInfo['password']), $_ENV);
896         }
897         $ahGzipPipes = null;
898         if (preg_match('/\\.gz$/', $sFilename)) {
899             $aDescriptors = array(
900                 0 => array('pipe', 'r'),
901                 1 => array('pipe', 'w'),
902                 2 => array('file', '/dev/null', 'a'),
903             );
904             $hGzipProcess = proc_open('zcat ' . $sFilename, $aDescriptors, $ahGzipPipes);
905             if (!is_resource($hGzipProcess)) {
906                 fail('unable to start zcat');
907             }
908
909             $aReadPipe = $ahGzipPipes[1];
910             fclose($ahGzipPipes[0]);
911         } else {
912             $sCMD .= ' -f ' . $sFilename;
913             $aReadPipe = array('pipe', 'r');
914         }
915         $aDescriptors = array(
916             0 => $aReadPipe,
917             1 => array('pipe', 'w'),
918             2 => array('file', '/dev/null', 'a'),
919         );
920         $ahPipes = null;
921         $hProcess = proc_open($sCMD, $aDescriptors, $ahPipes, null, $aProcEnv);
922         if (!is_resource($hProcess)) {
923             fail('unable to start pgsql');
924         }
925
926         // TODO: error checking
927         while (!feof($ahPipes[1])) {
928             echo fread($ahPipes[1], 4096);
929         }
930         fclose($ahPipes[1]);
931         $iReturn = proc_close($hProcess);
932         if ($iReturn > 0) {
933             fail("pgsql returned with error code ($iReturn)");
934         }
935         if ($ahGzipPipes) {
936             fclose($ahGzipPipes[1]);
937             proc_close($hGzipProcess);
938         }
939     }
940
941     private function replaceTablespace($sTemplate, $sTablespace, $sSql)
942     {
943         if ($sTablespace) {
944             $sSql = str_replace($sTemplate, 'TABLESPACE "' . $sTablespace . '"', $sSql);
945         } else {
946             $sSql = str_replace($sTemplate, '', $sSql);
947         }
948         return $sSql;
949     }
950 }