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