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