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