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