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