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