]> git.openstreetmap.org Git - nominatim.git/blob - utils/setup.php
setup: error out when web site user does not exist
[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     $i = chksql($oDB->getOne("select count(*) from pg_user where usename = '".CONST_Database_Web_User."'"));
134     if ($i == 0) {
135         echo "ERROR: Web user '".CONST_Database_Web_User."' does not exist. Create it with:\n";
136         echo "\n          createuser ".CONST_Database_Web_User."\n\n";
137         exit(1);
138     }
139
140     if (!file_exists(CONST_ExtraDataPath.'/country_osm_grid.sql.gz')) {
141         echo "Error: you need to download the country_osm_grid first:";
142         echo "\n    wget -O ".CONST_ExtraDataPath."/country_osm_grid.sql.gz http://www.nominatim.org/data/country_grid.sql.gz\n";
143         exit(1);
144     }
145
146     pgsqlRunScriptFile(CONST_BasePath.'/data/country_name.sql');
147     pgsqlRunScriptFile(CONST_BasePath.'/data/country_naturalearthdata.sql');
148     pgsqlRunScriptFile(CONST_BasePath.'/data/country_osm_grid.sql.gz');
149     pgsqlRunScriptFile(CONST_BasePath.'/data/gb_postcode_table.sql');
150     if (file_exists(CONST_BasePath.'/data/gb_postcode_data.sql.gz')) {
151         pgsqlRunScriptFile(CONST_BasePath.'/data/gb_postcode_data.sql.gz');
152     } else {
153         warn('external UK postcode table not found.');
154     }
155     if (CONST_Use_Extra_US_Postcodes) {
156         pgsqlRunScriptFile(CONST_BasePath.'/data/us_postcode.sql');
157     }
158
159     if ($aCMDResult['no-partitions']) {
160         pgsqlRunScript('update country_name set partition = 0');
161     }
162
163     // the following will be needed by create_functions later but
164     // is only defined in the subsequently called create_tables.
165     // Create dummies here that will be overwritten by the proper
166     // versions in create-tables.
167     pgsqlRunScript('CREATE TABLE place_boundingbox ()');
168     pgsqlRunScript('create type wikipedia_article_match as ()');
169 }
170
171 if ($aCMDResult['import-data'] || $aCMDResult['all']) {
172     info('Import data');
173     $bDidSomething = true;
174
175     $osm2pgsql = CONST_Osm2pgsql_Binary;
176     if (!file_exists($osm2pgsql)) {
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 '$osm2pgsql'");
180     }
181
182     if (!is_null(CONST_Osm2pgsql_Flatnode_File)) {
183         $osm2pgsql .= ' --flat-nodes '.CONST_Osm2pgsql_Flatnode_File;
184     }
185     if (CONST_Tablespace_Osm2pgsql_Data)
186         $osm2pgsql .= ' --tablespace-slim-data '.CONST_Tablespace_Osm2pgsql_Data;
187     if (CONST_Tablespace_Osm2pgsql_Index)
188         $osm2pgsql .= ' --tablespace-slim-index '.CONST_Tablespace_Osm2pgsql_Index;
189     if (CONST_Tablespace_Place_Data)
190         $osm2pgsql .= ' --tablespace-main-data '.CONST_Tablespace_Place_Data;
191     if (CONST_Tablespace_Place_Index)
192         $osm2pgsql .= ' --tablespace-main-index '.CONST_Tablespace_Place_Index;
193     $osm2pgsql .= ' -lsc -O gazetteer --hstore --number-processes 1';
194     $osm2pgsql .= ' -C '.$iCacheMemory;
195     $osm2pgsql .= ' -P '.$aDSNInfo['port'];
196     $osm2pgsql .= ' -d '.$aDSNInfo['database'].' '.$aCMDResult['osm-file'];
197     passthruCheckReturn($osm2pgsql);
198
199     $oDB =& getDB();
200     if (!$aCMDResult['ignore-errors'] && !chksql($oDB->getRow('select * from place limit 1'))) {
201         fail('No Data');
202     }
203 }
204
205 if ($aCMDResult['create-functions'] || $aCMDResult['all']) {
206     info('Create Functions');
207     $bDidSomething = true;
208     if (!file_exists(CONST_InstallPath.'/module/nominatim.so')) {
209         fail("nominatim module not built");
210     }
211     create_sql_functions($aCMDResult);
212 }
213
214 if ($aCMDResult['create-tables'] || $aCMDResult['all']) {
215     info('Create Tables');
216     $bDidSomething = true;
217
218     $sTemplate = file_get_contents(CONST_BasePath.'/sql/tables.sql');
219     $sTemplate = str_replace('{www-user}', CONST_Database_Web_User, $sTemplate);
220     $sTemplate = replace_tablespace(
221         '{ts:address-data}',
222         CONST_Tablespace_Address_Data,
223         $sTemplate
224     );
225     $sTemplate = replace_tablespace(
226         '{ts:address-index}',
227         CONST_Tablespace_Address_Index,
228         $sTemplate
229     );
230     $sTemplate = replace_tablespace(
231         '{ts:search-data}',
232         CONST_Tablespace_Search_Data,
233         $sTemplate
234     );
235     $sTemplate = replace_tablespace(
236         '{ts:search-index}',
237         CONST_Tablespace_Search_Index,
238         $sTemplate
239     );
240     $sTemplate = replace_tablespace(
241         '{ts:aux-data}',
242         CONST_Tablespace_Aux_Data,
243         $sTemplate
244     );
245     $sTemplate = replace_tablespace(
246         '{ts:aux-index}',
247         CONST_Tablespace_Aux_Index,
248         $sTemplate
249     );
250     pgsqlRunScript($sTemplate, false);
251
252     // re-run the functions
253     info('Recreate Functions');
254     create_sql_functions($aCMDResult);
255 }
256
257 if ($aCMDResult['create-partition-tables'] || $aCMDResult['all']) {
258     info('Create Partition Tables');
259     $bDidSomething = true;
260
261     $sTemplate = file_get_contents(CONST_BasePath.'/sql/partition-tables.src.sql');
262     $sTemplate = replace_tablespace(
263         '{ts:address-data}',
264         CONST_Tablespace_Address_Data,
265         $sTemplate
266     );
267     $sTemplate = replace_tablespace(
268         '{ts:address-index}',
269         CONST_Tablespace_Address_Index,
270         $sTemplate
271     );
272     $sTemplate = replace_tablespace(
273         '{ts:search-data}',
274         CONST_Tablespace_Search_Data,
275         $sTemplate
276     );
277     $sTemplate = replace_tablespace(
278         '{ts:search-index}',
279         CONST_Tablespace_Search_Index,
280         $sTemplate
281     );
282     $sTemplate = replace_tablespace(
283         '{ts:aux-data}',
284         CONST_Tablespace_Aux_Data,
285         $sTemplate
286     );
287     $sTemplate = replace_tablespace(
288         '{ts:aux-index}',
289         CONST_Tablespace_Aux_Index,
290         $sTemplate
291     );
292
293     pgsqlRunPartitionScript($sTemplate);
294 }
295
296
297 if ($aCMDResult['create-partition-functions'] || $aCMDResult['all']) {
298     info('Create Partition Functions');
299     $bDidSomething = true;
300
301     $sTemplate = file_get_contents(CONST_BasePath.'/sql/partition-functions.src.sql');
302
303     pgsqlRunPartitionScript($sTemplate);
304 }
305
306 if ($aCMDResult['import-wikipedia-articles'] || $aCMDResult['all']) {
307     $bDidSomething = true;
308     $sWikiArticlesFile = CONST_Wikipedia_Data_Path.'/wikipedia_article.sql.bin';
309     $sWikiRedirectsFile = CONST_Wikipedia_Data_Path.'/wikipedia_redirect.sql.bin';
310     if (file_exists($sWikiArticlesFile)) {
311         info('Importing wikipedia articles');
312         pgsqlRunDropAndRestore($sWikiArticlesFile);
313     } else {
314         warn('wikipedia article dump file not found - places will have default importance');
315     }
316     if (file_exists($sWikiRedirectsFile)) {
317         info('Importing wikipedia redirects');
318         pgsqlRunDropAndRestore($sWikiRedirectsFile);
319     } else {
320         warn('wikipedia redirect dump file not found - some place importance values may be missing');
321     }
322 }
323
324
325 if ($aCMDResult['load-data'] || $aCMDResult['all']) {
326     info('Drop old Data');
327     $bDidSomething = true;
328
329     $oDB =& getDB();
330     if (!pg_query($oDB->connection, 'TRUNCATE word')) fail(pg_last_error($oDB->connection));
331     echo '.';
332     if (!pg_query($oDB->connection, 'TRUNCATE placex')) fail(pg_last_error($oDB->connection));
333     echo '.';
334     if (!pg_query($oDB->connection, 'TRUNCATE location_property_osmline')) fail(pg_last_error($oDB->connection));
335     echo '.';
336     if (!pg_query($oDB->connection, 'TRUNCATE place_addressline')) fail(pg_last_error($oDB->connection));
337     echo '.';
338     if (!pg_query($oDB->connection, 'TRUNCATE place_boundingbox')) fail(pg_last_error($oDB->connection));
339     echo '.';
340     if (!pg_query($oDB->connection, 'TRUNCATE location_area')) fail(pg_last_error($oDB->connection));
341     echo '.';
342     if (!pg_query($oDB->connection, 'TRUNCATE search_name')) fail(pg_last_error($oDB->connection));
343     echo '.';
344     if (!pg_query($oDB->connection, 'TRUNCATE search_name_blank')) fail(pg_last_error($oDB->connection));
345     echo '.';
346     if (!pg_query($oDB->connection, 'DROP SEQUENCE seq_place')) fail(pg_last_error($oDB->connection));
347     echo '.';
348     if (!pg_query($oDB->connection, 'CREATE SEQUENCE seq_place start 100000')) fail(pg_last_error($oDB->connection));
349     echo '.';
350
351     $sSQL = 'select distinct partition from country_name';
352     $aPartitions = chksql($oDB->getCol($sSQL));
353     if (!$aCMDResult['no-partitions']) $aPartitions[] = 0;
354     foreach ($aPartitions as $sPartition) {
355         if (!pg_query($oDB->connection, 'TRUNCATE location_road_'.$sPartition)) fail(pg_last_error($oDB->connection));
356         echo '.';
357     }
358
359     // used by getorcreate_word_id to ignore frequent partial words
360     $sSQL = 'CREATE OR REPLACE FUNCTION get_maxwordfreq() RETURNS integer AS ';
361     $sSQL .= '$$ SELECT '.CONST_Max_Word_Frequency.' as maxwordfreq; $$ LANGUAGE SQL IMMUTABLE';
362     if (!pg_query($oDB->connection, $sSQL)) {
363         fail(pg_last_error($oDB->connection));
364     }
365     echo ".\n";
366
367     // pre-create the word list
368     if (!$aCMDResult['disable-token-precalc']) {
369         info('Loading word list');
370         pgsqlRunScriptFile(CONST_BasePath.'/data/words.sql');
371     }
372
373     info('Load Data');
374     $sColumns = 'osm_type, osm_id, class, type, name, admin_level, address, extratags, geometry';
375
376     $aDBInstances = array();
377     $iLoadThreads = max(1, $iInstances - 1);
378     for ($i = 0; $i < $iLoadThreads; $i++) {
379         $aDBInstances[$i] =& getDB(true);
380         $sSQL = "INSERT INTO placex ($sColumns) SELECT $sColumns FROM place WHERE osm_id % $iLoadThreads = $i";
381         $sSQL .= " and not (class='place' and type='houses' and osm_type='W'";
382         $sSQL .= "          and ST_GeometryType(geometry) = 'ST_LineString')";
383         $sSQL .= " and ST_IsValid(geometry)";
384         if ($aCMDResult['verbose']) echo "$sSQL\n";
385         if (!pg_send_query($aDBInstances[$i]->connection, $sSQL)) {
386             fail(pg_last_error($aDBInstances[$i]->connection));
387         }
388     }
389     // last thread for interpolation lines
390     $aDBInstances[$iLoadThreads] =& getDB(true);
391     $sSQL = 'insert into location_property_osmline';
392     $sSQL .= ' (osm_id, address, linegeo)';
393     $sSQL .= ' SELECT osm_id, address, geometry from place where ';
394     $sSQL .= "class='place' and type='houses' and osm_type='W' and ST_GeometryType(geometry) = 'ST_LineString'";
395     if ($aCMDResult['verbose']) echo "$sSQL\n";
396     if (!pg_send_query($aDBInstances[$iLoadThreads]->connection, $sSQL)) {
397         fail(pg_last_error($aDBInstances[$iLoadThreads]->connection));
398     }
399
400     $bAnyBusy = true;
401     while ($bAnyBusy) {
402         $bAnyBusy = false;
403         for ($i = 0; $i <= $iLoadThreads; $i++) {
404             if (pg_connection_busy($aDBInstances[$i]->connection)) $bAnyBusy = true;
405         }
406         sleep(1);
407         echo '.';
408     }
409     echo "\n";
410     info('Reanalysing database');
411     pgsqlRunScript('ANALYSE');
412
413     $sDatabaseDate = getDatabaseDate($oDB);
414     pg_query($oDB->connection, 'TRUNCATE import_status');
415     if ($sDatabaseDate === false) {
416         warn('could not determine database date.');
417     } else {
418         $sSQL = "INSERT INTO import_status (lastimportdate) VALUES('".$sDatabaseDate."')";
419         pg_query($oDB->connection, $sSQL);
420         echo "Latest data imported from $sDatabaseDate.\n";
421     }
422 }
423
424 if ($aCMDResult['import-tiger-data']) {
425     info('Import Tiger data');
426     $bDidSomething = true;
427
428     $sTemplate = file_get_contents(CONST_BasePath.'/sql/tiger_import_start.sql');
429     $sTemplate = str_replace('{www-user}', CONST_Database_Web_User, $sTemplate);
430     $sTemplate = replace_tablespace(
431         '{ts:aux-data}',
432         CONST_Tablespace_Aux_Data,
433         $sTemplate
434     );
435     $sTemplate = replace_tablespace(
436         '{ts:aux-index}',
437         CONST_Tablespace_Aux_Index,
438         $sTemplate
439     );
440     pgsqlRunScript($sTemplate, false);
441
442     $aDBInstances = array();
443     for ($i = 0; $i < $iInstances; $i++) {
444         $aDBInstances[$i] =& getDB(true);
445     }
446
447     foreach (glob(CONST_Tiger_Data_Path.'/*.sql') as $sFile) {
448         echo $sFile.': ';
449         $hFile = fopen($sFile, "r");
450         $sSQL = fgets($hFile, 100000);
451         $iLines = 0;
452
453         while (true) {
454             for ($i = 0; $i < $iInstances; $i++) {
455                 if (!pg_connection_busy($aDBInstances[$i]->connection)) {
456                     while (pg_get_result($aDBInstances[$i]->connection));
457                     $sSQL = fgets($hFile, 100000);
458                     if (!$sSQL) break 2;
459                     if (!pg_send_query($aDBInstances[$i]->connection, $sSQL)) fail(pg_last_error($oDB->connection));
460                     $iLines++;
461                     if ($iLines == 1000) {
462                         echo ".";
463                         $iLines = 0;
464                     }
465                 }
466             }
467             usleep(10);
468         }
469
470         fclose($hFile);
471
472         $bAnyBusy = true;
473         while ($bAnyBusy) {
474             $bAnyBusy = false;
475             for ($i = 0; $i < $iInstances; $i++) {
476                 if (pg_connection_busy($aDBInstances[$i]->connection)) $bAnyBusy = true;
477             }
478             usleep(10);
479         }
480         echo "\n";
481     }
482
483     info('Creating indexes on Tiger data');
484     $sTemplate = file_get_contents(CONST_BasePath.'/sql/tiger_import_finish.sql');
485     $sTemplate = str_replace('{www-user}', CONST_Database_Web_User, $sTemplate);
486     $sTemplate = replace_tablespace(
487         '{ts:aux-data}',
488         CONST_Tablespace_Aux_Data,
489         $sTemplate
490     );
491     $sTemplate = replace_tablespace(
492         '{ts:aux-index}',
493         CONST_Tablespace_Aux_Index,
494         $sTemplate
495     );
496     pgsqlRunScript($sTemplate, false);
497 }
498
499 if ($aCMDResult['calculate-postcodes'] || $aCMDResult['all']) {
500     info('Calculate Postcodes');
501     $bDidSomething = true;
502     $oDB =& getDB();
503     if (!pg_query($oDB->connection, 'TRUNCATE location_postcode')) {
504         fail(pg_last_error($oDB->connection));
505     }
506
507     $sSQL  = "INSERT INTO location_postcode";
508     $sSQL .= " (place_id, indexed_status, country_code, postcode, geometry) ";
509     $sSQL .= "SELECT nextval('seq_place'), 1, country_code,";
510     $sSQL .= "       upper(trim (both ' ' from address->'postcode')) as pc,";
511     $sSQL .= "       ST_Centroid(ST_Collect(ST_Centroid(geometry)))";
512     $sSQL .= "  FROM placex";
513     $sSQL .= " WHERE address ? 'postcode' AND address->'postcode' NOT SIMILAR TO '%(,|;)%'";
514     $sSQL .= "       AND geometry IS NOT null";
515     $sSQL .= " GROUP BY country_code, pc";
516
517     if (!pg_query($oDB->connection, $sSQL)) {
518         fail(pg_last_error($oDB->connection));
519     }
520
521     if (CONST_Use_Extra_US_Postcodes) {
522         // only add postcodes that are not yet available in OSM
523         $sSQL  = "INSERT INTO location_postcode";
524         $sSQL .= " (place_id, indexed_status, country_code, postcode, geometry) ";
525         $sSQL .= "SELECT nextval('seq_place'), 1, 'us', postcode,";
526         $sSQL .= "       ST_SetSRID(ST_Point(x,y),4326)";
527         $sSQL .= "  FROM us_postcode WHERE postcode NOT IN";
528         $sSQL .= "        (SELECT postcode FROM location_postcode";
529         $sSQL .= "          WHERE country_code = 'us')";
530
531         if (!pg_query($oDB->connection, $sSQL)) fail(pg_last_error($oDB->connection));
532     }
533
534     // add missing postcodes for GB (if available)
535     $sSQL  = "INSERT INTO location_postcode";
536     $sSQL .= " (place_id, indexed_status, country_code, postcode, geometry) ";
537     $sSQL .= "SELECT nextval('seq_place'), 1, 'gb', postcode, geometry";
538     $sSQL .= "  FROM gb_postcode WHERE postcode NOT IN";
539     $sSQL .= "           (SELECT postcode FROM location_postcode";
540     $sSQL .= "             WHERE country_code = 'gb')";
541     if (!pg_query($oDB->connection, $sSQL)) fail(pg_last_error($oDB->connection));
542
543     if (!$aCMDResult['all']) {
544         $sSQL = "DELETE FROM word WHERE class='place' and type='postcode'";
545         $sSQL .= "and word NOT IN (SELECT postcode FROM location_postcode)";
546         if (!pg_query($oDB->connection, $sSQL)) {
547             fail(pg_last_error($oDB->connection));
548         }
549     }
550     $sSQL = "SELECT count(getorcreate_postcode_id(v)) FROM ";
551     $sSQL .= "(SELECT distinct(postcode) as v FROM location_postcode) p";
552
553     if (!pg_query($oDB->connection, $sSQL)) {
554         fail(pg_last_error($oDB->connection));
555     }
556 }
557
558 if ($aCMDResult['osmosis-init']) {
559     $bDidSomething = true;
560     echo "Command 'osmosis-init' no longer available, please use utils/update.php --init-updates.\n";
561 }
562
563 if ($aCMDResult['index'] || $aCMDResult['all']) {
564     $bDidSomething = true;
565     $sOutputFile = '';
566     $sBaseCmd = CONST_InstallPath.'/nominatim/nominatim -i -d '.$aDSNInfo['database'].' -P '.$aDSNInfo['port'].' -t '.$iInstances.$sOutputFile;
567     info('Index ranks 0 - 4');
568     passthruCheckReturn($sBaseCmd.' -R 4');
569     if (!$aCMDResult['index-noanalyse']) pgsqlRunScript('ANALYSE');
570     info('Index ranks 5 - 25');
571     passthruCheckReturn($sBaseCmd.' -r 5 -R 25');
572     if (!$aCMDResult['index-noanalyse']) pgsqlRunScript('ANALYSE');
573     info('Index ranks 26 - 30');
574     passthruCheckReturn($sBaseCmd.' -r 26');
575
576     info('Index postcodes');
577     $oDB =& getDB();
578     $sSQL = 'UPDATE location_postcode SET indexed_status = 0';
579     if (!pg_query($oDB->connection, $sSQL)) fail(pg_last_error($oDB->connection));
580 }
581
582 if ($aCMDResult['create-search-indices'] || $aCMDResult['all']) {
583     info('Create Search indices');
584     $bDidSomething = true;
585
586     $sTemplate = file_get_contents(CONST_BasePath.'/sql/indices.src.sql');
587     $sTemplate = replace_tablespace(
588         '{ts:address-index}',
589         CONST_Tablespace_Address_Index,
590         $sTemplate
591     );
592     $sTemplate = replace_tablespace(
593         '{ts:search-index}',
594         CONST_Tablespace_Search_Index,
595         $sTemplate
596     );
597     $sTemplate = replace_tablespace(
598         '{ts:aux-index}',
599         CONST_Tablespace_Aux_Index,
600         $sTemplate
601     );
602
603     pgsqlRunScript($sTemplate);
604 }
605
606 if ($aCMDResult['create-country-names'] || $aCMDResult['all']) {
607     info('Create search index for default country names');
608     $bDidSomething = true;
609
610     pgsqlRunScript("select getorcreate_country(make_standard_name('uk'), 'gb')");
611     pgsqlRunScript("select getorcreate_country(make_standard_name('united states'), 'us')");
612     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");
613     pgsqlRunScript("select count(*) from (select getorcreate_country(make_standard_name(name->'name'), country_code) from country_name where name ? 'name') as x");
614
615     $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 ';
616     if (CONST_Languages) {
617         $sSQL .= 'in ';
618         $sDelim = '(';
619         foreach (explode(',', CONST_Languages) as $sLang) {
620             $sSQL .= $sDelim."'name:$sLang'";
621             $sDelim = ',';
622         }
623         $sSQL .= ')';
624     } else {
625         // all include all simple name tags
626         $sSQL .= "like 'name:%'";
627     }
628     $sSQL .= ') v';
629     pgsqlRunScript($sSQL);
630 }
631
632 if ($aCMDResult['drop']) {
633     info('Drop tables only required for updates');
634     // The implementation is potentially a bit dangerous because it uses
635     // a positive selection of tables to keep, and deletes everything else.
636     // Including any tables that the unsuspecting user might have manually
637     // created. USE AT YOUR OWN PERIL.
638     $bDidSomething = true;
639
640     // tables we want to keep. everything else goes.
641     $aKeepTables = array(
642                     "*columns",
643                     "import_polygon_*",
644                     "import_status",
645                     "place_addressline",
646                     "location_property*",
647                     "placex",
648                     "search_name",
649                     "seq_*",
650                     "word",
651                     "query_log",
652                     "new_query_log",
653                     "gb_postcode",
654                     "spatial_ref_sys",
655                     "country_name",
656                     "place_classtype_*"
657                    );
658
659     $oDB =& getDB();
660     $aDropTables = array();
661     $aHaveTables = chksql($oDB->getCol("SELECT tablename FROM pg_tables WHERE schemaname='public'"));
662
663     foreach ($aHaveTables as $sTable) {
664         $bFound = false;
665         foreach ($aKeepTables as $sKeep) {
666             if (fnmatch($sKeep, $sTable)) {
667                 $bFound = true;
668                 break;
669             }
670         }
671         if (!$bFound) array_push($aDropTables, $sTable);
672     }
673
674     foreach ($aDropTables as $sDrop) {
675         if ($aCMDResult['verbose']) echo "dropping table $sDrop\n";
676         @pg_query($oDB->connection, "DROP TABLE $sDrop CASCADE");
677         // ignore warnings/errors as they might be caused by a table having
678         // been deleted already by CASCADE
679     }
680
681     if (!is_null(CONST_Osm2pgsql_Flatnode_File)) {
682         if ($aCMDResult['verbose']) echo "deleting ".CONST_Osm2pgsql_Flatnode_File."\n";
683         unlink(CONST_Osm2pgsql_Flatnode_File);
684     }
685 }
686
687 if (!$bDidSomething) {
688     showUsage($aCMDOptions, true);
689 } else {
690     echo "Summary of warnings:\n\n";
691     repeatWarnings();
692     echo "\n";
693     info('Setup finished.');
694 }
695
696
697 function pgsqlRunScriptFile($sFilename)
698 {
699     global $aCMDResult;
700     if (!file_exists($sFilename)) fail('unable to find '.$sFilename);
701
702     // Convert database DSN to psql parameters
703     $aDSNInfo = DB::parseDSN(CONST_Database_DSN);
704     if (!isset($aDSNInfo['port']) || !$aDSNInfo['port']) $aDSNInfo['port'] = 5432;
705     $sCMD = 'psql -p '.$aDSNInfo['port'].' -d '.$aDSNInfo['database'];
706     if (!$aCMDResult['verbose']) {
707         $sCMD .= ' -q';
708     }
709
710     $ahGzipPipes = null;
711     if (preg_match('/\\.gz$/', $sFilename)) {
712         $aDescriptors = array(
713                          0 => array('pipe', 'r'),
714                          1 => array('pipe', 'w'),
715                          2 => array('file', '/dev/null', 'a')
716                         );
717         $hGzipProcess = proc_open('zcat '.$sFilename, $aDescriptors, $ahGzipPipes);
718         if (!is_resource($hGzipProcess)) fail('unable to start zcat');
719         $aReadPipe = $ahGzipPipes[1];
720         fclose($ahGzipPipes[0]);
721     } else {
722         $sCMD .= ' -f '.$sFilename;
723         $aReadPipe = array('pipe', 'r');
724     }
725
726     $aDescriptors = array(
727                      0 => $aReadPipe,
728                      1 => array('pipe', 'w'),
729                      2 => array('file', '/dev/null', 'a')
730                     );
731     $ahPipes = null;
732     $hProcess = proc_open($sCMD, $aDescriptors, $ahPipes);
733     if (!is_resource($hProcess)) fail('unable to start pgsql');
734
735
736     // TODO: error checking
737     while (!feof($ahPipes[1])) {
738         echo fread($ahPipes[1], 4096);
739     }
740     fclose($ahPipes[1]);
741
742     $iReturn = proc_close($hProcess);
743     if ($iReturn > 0) {
744         fail("pgsql returned with error code ($iReturn)");
745     }
746     if ($ahGzipPipes) {
747         fclose($ahGzipPipes[1]);
748         proc_close($hGzipProcess);
749     }
750 }
751
752 function pgsqlRunScript($sScript, $bfatal = true)
753 {
754     global $aCMDResult;
755     // Convert database DSN to psql parameters
756     $aDSNInfo = DB::parseDSN(CONST_Database_DSN);
757     if (!isset($aDSNInfo['port']) || !$aDSNInfo['port']) $aDSNInfo['port'] = 5432;
758     $sCMD = 'psql -p '.$aDSNInfo['port'].' -d '.$aDSNInfo['database'];
759     if (!$aCMDResult['verbose']) {
760         $sCMD .= ' -q';
761     }
762     if ($bfatal && !$aCMDResult['ignore-errors'])
763         $sCMD .= ' -v ON_ERROR_STOP=1';
764     $aDescriptors = array(
765                      0 => array('pipe', 'r'),
766                      1 => STDOUT,
767                      2 => STDERR
768                     );
769     $ahPipes = null;
770     $hProcess = @proc_open($sCMD, $aDescriptors, $ahPipes);
771     if (!is_resource($hProcess)) fail('unable to start pgsql');
772
773     while (strlen($sScript)) {
774         $written = fwrite($ahPipes[0], $sScript);
775         if ($written <= 0) break;
776         $sScript = substr($sScript, $written);
777     }
778     fclose($ahPipes[0]);
779     $iReturn = proc_close($hProcess);
780     if ($bfatal && $iReturn > 0) {
781         fail("pgsql returned with error code ($iReturn)");
782     }
783 }
784
785 function pgsqlRunPartitionScript($sTemplate)
786 {
787     global $aCMDResult;
788     $oDB =& getDB();
789
790     $sSQL = 'select distinct partition from country_name';
791     $aPartitions = chksql($oDB->getCol($sSQL));
792     if (!$aCMDResult['no-partitions']) $aPartitions[] = 0;
793
794     preg_match_all('#^-- start(.*?)^-- end#ms', $sTemplate, $aMatches, PREG_SET_ORDER);
795     foreach ($aMatches as $aMatch) {
796         $sResult = '';
797         foreach ($aPartitions as $sPartitionName) {
798             $sResult .= str_replace('-partition-', $sPartitionName, $aMatch[1]);
799         }
800         $sTemplate = str_replace($aMatch[0], $sResult, $sTemplate);
801     }
802
803     pgsqlRunScript($sTemplate);
804 }
805
806 function pgsqlRunRestoreData($sDumpFile)
807 {
808     // Convert database DSN to psql parameters
809     $aDSNInfo = DB::parseDSN(CONST_Database_DSN);
810     if (!isset($aDSNInfo['port']) || !$aDSNInfo['port']) $aDSNInfo['port'] = 5432;
811     $sCMD = 'pg_restore -p '.$aDSNInfo['port'].' -d '.$aDSNInfo['database'].' -Fc -a '.$sDumpFile;
812
813     $aDescriptors = array(
814                      0 => array('pipe', 'r'),
815                      1 => array('pipe', 'w'),
816                      2 => array('file', '/dev/null', 'a')
817                     );
818     $ahPipes = null;
819     $hProcess = proc_open($sCMD, $aDescriptors, $ahPipes);
820     if (!is_resource($hProcess)) fail('unable to start pg_restore');
821
822     fclose($ahPipes[0]);
823
824     // TODO: error checking
825     while (!feof($ahPipes[1])) {
826         echo fread($ahPipes[1], 4096);
827     }
828     fclose($ahPipes[1]);
829
830     $iReturn = proc_close($hProcess);
831 }
832
833 function pgsqlRunDropAndRestore($sDumpFile)
834 {
835     // Convert database DSN to psql parameters
836     $aDSNInfo = DB::parseDSN(CONST_Database_DSN);
837     if (!isset($aDSNInfo['port']) || !$aDSNInfo['port']) $aDSNInfo['port'] = 5432;
838     $sCMD = 'pg_restore -p '.$aDSNInfo['port'].' -d '.$aDSNInfo['database'].' -Fc --clean '.$sDumpFile;
839
840     $aDescriptors = array(
841                      0 => array('pipe', 'r'),
842                      1 => array('pipe', 'w'),
843                      2 => array('file', '/dev/null', 'a')
844                     );
845     $ahPipes = null;
846     $hProcess = proc_open($sCMD, $aDescriptors, $ahPipes);
847     if (!is_resource($hProcess)) fail('unable to start pg_restore');
848
849     fclose($ahPipes[0]);
850
851     // TODO: error checking
852     while (!feof($ahPipes[1])) {
853         echo fread($ahPipes[1], 4096);
854     }
855     fclose($ahPipes[1]);
856
857     $iReturn = proc_close($hProcess);
858 }
859
860 function passthruCheckReturn($cmd)
861 {
862     $result = -1;
863     passthru($cmd, $result);
864     if ($result != 0) fail('Error executing external command: '.$cmd);
865 }
866
867 function replace_tablespace($sTemplate, $sTablespace, $sSql)
868 {
869     if ($sTablespace) {
870         $sSql = str_replace($sTemplate, 'TABLESPACE "'.$sTablespace.'"', $sSql);
871     } else {
872         $sSql = str_replace($sTemplate, '', $sSql);
873     }
874
875     return $sSql;
876 }
877
878 function create_sql_functions($aCMDResult)
879 {
880     $sTemplate = file_get_contents(CONST_BasePath.'/sql/functions.sql');
881     $sTemplate = str_replace('{modulepath}', CONST_InstallPath.'/module', $sTemplate);
882     if ($aCMDResult['enable-diff-updates']) {
883         $sTemplate = str_replace('RETURN NEW; -- %DIFFUPDATES%', '--', $sTemplate);
884     }
885     if ($aCMDResult['enable-debug-statements']) {
886         $sTemplate = str_replace('--DEBUG:', '', $sTemplate);
887     }
888     if (CONST_Limit_Reindexing) {
889         $sTemplate = str_replace('--LIMIT INDEXING:', '', $sTemplate);
890     }
891     if (!CONST_Use_US_Tiger_Data) {
892         $sTemplate = str_replace('-- %NOTIGERDATA% ', '', $sTemplate);
893     }
894     if (!CONST_Use_Aux_Location_data) {
895         $sTemplate = str_replace('-- %NOAUXDATA% ', '', $sTemplate);
896     }
897     pgsqlRunScript($sTemplate);
898 }