]> git.openstreetmap.org Git - nominatim.git/blob - lib-php/setup/SetupClass.php
34c97319ddce0b4001605ad5335cd887fa37ceb3
[nominatim.git] / lib-php / setup / SetupClass.php
1 <?php
2
3 namespace Nominatim\Setup;
4
5 require_once(CONST_LibDir.'/Shell.php');
6
7 class SetupFunctions
8 {
9     protected $iInstances;
10     protected $aDSNInfo;
11     protected $bQuiet;
12     protected $bVerbose;
13     protected $sIgnoreErrors;
14     protected $bEnableDiffUpdates;
15     protected $bEnableDebugStatements;
16     protected $bNoPartitions;
17     protected $bDrop;
18     protected $oDB = null;
19     protected $oNominatimCmd;
20
21     public function __construct(array $aCMDResult)
22     {
23         // by default, use all but one processor, but never more than 15.
24         $this->iInstances = isset($aCMDResult['threads'])
25             ? $aCMDResult['threads']
26             : (min(16, getProcessorCount()) - 1);
27
28         if ($this->iInstances < 1) {
29             $this->iInstances = 1;
30             warn('resetting threads to '.$this->iInstances);
31         }
32
33         // parse database string
34         $this->aDSNInfo = \Nominatim\DB::parseDSN(getSetting('DATABASE_DSN'));
35         if (!isset($this->aDSNInfo['port'])) {
36             $this->aDSNInfo['port'] = 5432;
37         }
38
39         // setting member variables based on command line options stored in $aCMDResult
40         $this->bQuiet = isset($aCMDResult['quiet']) && $aCMDResult['quiet'];
41         $this->bVerbose = $aCMDResult['verbose'];
42
43         //setting default values which are not set by the update.php array
44         if (isset($aCMDResult['ignore-errors'])) {
45             $this->sIgnoreErrors = $aCMDResult['ignore-errors'];
46         } else {
47             $this->sIgnoreErrors = false;
48         }
49         if (isset($aCMDResult['enable-debug-statements'])) {
50             $this->bEnableDebugStatements = $aCMDResult['enable-debug-statements'];
51         } else {
52             $this->bEnableDebugStatements = false;
53         }
54         if (isset($aCMDResult['no-partitions'])) {
55             $this->bNoPartitions = $aCMDResult['no-partitions'];
56         } else {
57             $this->bNoPartitions = false;
58         }
59         if (isset($aCMDResult['enable-diff-updates'])) {
60             $this->bEnableDiffUpdates = $aCMDResult['enable-diff-updates'];
61         } else {
62             $this->bEnableDiffUpdates = false;
63         }
64
65         $this->bDrop = isset($aCMDResult['drop']) && $aCMDResult['drop'];
66
67         $this->oNominatimCmd = new \Nominatim\Shell(getSetting('NOMINATIM_TOOL'));
68         if ($this->bQuiet) {
69             $this->oNominatimCmd->addParams('--quiet');
70         }
71         if ($this->bVerbose) {
72             $this->oNominatimCmd->addParams('--verbose');
73         }
74         $this->oNominatimCmd->addParams('--threads', $this->iInstances);
75     }
76
77     public function createFunctions()
78     {
79         info('Create Functions');
80
81         // Try accessing the C module, so we know early if something is wrong
82         $this->checkModulePresence(); // raises exception on failure
83
84         $this->createSqlFunctions();
85     }
86
87     public function createTables($bReverseOnly = false)
88     {
89         info('Create Tables');
90
91         $sTemplate = file_get_contents(CONST_SqlDir.'/tables.sql');
92         $sTemplate = $this->replaceSqlPatterns($sTemplate);
93
94         $this->pgsqlRunScript($sTemplate, false);
95
96         if ($bReverseOnly) {
97             $this->dropTable('search_name');
98         }
99
100         (clone($this->oNominatimCmd))->addParams('refresh', '--address-levels')->run();
101     }
102
103     public function createTableTriggers()
104     {
105         info('Create Tables');
106
107         $sTemplate = file_get_contents(CONST_SqlDir.'/table-triggers.sql');
108         $sTemplate = $this->replaceSqlPatterns($sTemplate);
109
110         $this->pgsqlRunScript($sTemplate, false);
111     }
112
113     public function createPartitionTables()
114     {
115         info('Create Partition Tables');
116
117         $sTemplate = file_get_contents(CONST_SqlDir.'/partition-tables.src.sql');
118         $sTemplate = $this->replaceSqlPatterns($sTemplate);
119
120         $this->pgsqlRunPartitionScript($sTemplate);
121     }
122
123     public function loadData($bDisableTokenPrecalc)
124     {
125         info('Drop old Data');
126
127         $oDB = $this->db();
128
129         $oDB->exec('TRUNCATE word');
130         echo '.';
131         $oDB->exec('TRUNCATE placex');
132         echo '.';
133         $oDB->exec('TRUNCATE location_property_osmline');
134         echo '.';
135         $oDB->exec('TRUNCATE place_addressline');
136         echo '.';
137         $oDB->exec('TRUNCATE location_area');
138         echo '.';
139         if (!$this->dbReverseOnly()) {
140             $oDB->exec('TRUNCATE search_name');
141             echo '.';
142         }
143         $oDB->exec('TRUNCATE search_name_blank');
144         echo '.';
145         $oDB->exec('DROP SEQUENCE seq_place');
146         echo '.';
147         $oDB->exec('CREATE SEQUENCE seq_place start 100000');
148         echo '.';
149
150         $sSQL = 'select distinct partition from country_name';
151         $aPartitions = $oDB->getCol($sSQL);
152
153         if (!$this->bNoPartitions) $aPartitions[] = 0;
154         foreach ($aPartitions as $sPartition) {
155             $oDB->exec('TRUNCATE location_road_'.$sPartition);
156             echo '.';
157         }
158
159         // used by getorcreate_word_id to ignore frequent partial words
160         $sSQL = 'CREATE OR REPLACE FUNCTION get_maxwordfreq() RETURNS integer AS ';
161         $sSQL .= '$$ SELECT '.getSetting('MAX_WORD_FREQUENCY').' as maxwordfreq; $$ LANGUAGE SQL IMMUTABLE';
162         $oDB->exec($sSQL);
163         echo ".\n";
164
165         // pre-create the word list
166         if (!$bDisableTokenPrecalc) {
167             info('Loading word list');
168             $this->pgsqlRunScriptFile(CONST_DataDir.'/words.sql');
169         }
170
171         info('Load Data');
172         $sColumns = 'osm_type, osm_id, class, type, name, admin_level, address, extratags, geometry';
173
174         $aDBInstances = array();
175         $iLoadThreads = max(1, $this->iInstances - 1);
176         for ($i = 0; $i < $iLoadThreads; $i++) {
177             // https://secure.php.net/manual/en/function.pg-connect.php
178             $DSN = getSetting('DATABASE_DSN');
179             $DSN = preg_replace('/^pgsql:/', '', $DSN);
180             $DSN = preg_replace('/;/', ' ', $DSN);
181             $aDBInstances[$i] = pg_connect($DSN, PGSQL_CONNECT_FORCE_NEW);
182             pg_ping($aDBInstances[$i]);
183         }
184
185         for ($i = 0; $i < $iLoadThreads; $i++) {
186             $sSQL = "INSERT INTO placex ($sColumns) SELECT $sColumns FROM place WHERE osm_id % $iLoadThreads = $i";
187             $sSQL .= " and not (class='place' and type='houses' and osm_type='W'";
188             $sSQL .= "          and ST_GeometryType(geometry) = 'ST_LineString')";
189             $sSQL .= ' and ST_IsValid(geometry)';
190             if ($this->bVerbose) echo "$sSQL\n";
191             if (!pg_send_query($aDBInstances[$i], $sSQL)) {
192                 fail(pg_last_error($aDBInstances[$i]));
193             }
194         }
195
196         // last thread for interpolation lines
197         // https://secure.php.net/manual/en/function.pg-connect.php
198         $DSN = getSetting('DATABASE_DSN');
199         $DSN = preg_replace('/^pgsql:/', '', $DSN);
200         $DSN = preg_replace('/;/', ' ', $DSN);
201         $aDBInstances[$iLoadThreads] = pg_connect($DSN, PGSQL_CONNECT_FORCE_NEW);
202         pg_ping($aDBInstances[$iLoadThreads]);
203         $sSQL = 'insert into location_property_osmline';
204         $sSQL .= ' (osm_id, address, linegeo)';
205         $sSQL .= ' SELECT osm_id, address, geometry from place where ';
206         $sSQL .= "class='place' and type='houses' and osm_type='W' and ST_GeometryType(geometry) = 'ST_LineString'";
207         if ($this->bVerbose) echo "$sSQL\n";
208         if (!pg_send_query($aDBInstances[$iLoadThreads], $sSQL)) {
209             fail(pg_last_error($aDBInstances[$iLoadThreads]));
210         }
211
212         $bFailed = false;
213         for ($i = 0; $i <= $iLoadThreads; $i++) {
214             while (($hPGresult = pg_get_result($aDBInstances[$i])) !== false) {
215                 $resultStatus = pg_result_status($hPGresult);
216                 // PGSQL_EMPTY_QUERY, PGSQL_COMMAND_OK, PGSQL_TUPLES_OK,
217                 // PGSQL_COPY_OUT, PGSQL_COPY_IN, PGSQL_BAD_RESPONSE,
218                 // PGSQL_NONFATAL_ERROR and PGSQL_FATAL_ERROR
219                 // echo 'Query result ' . $i . ' is: ' . $resultStatus . "\n";
220                 if ($resultStatus != PGSQL_COMMAND_OK && $resultStatus != PGSQL_TUPLES_OK) {
221                     $resultError = pg_result_error($hPGresult);
222                     echo '-- error text ' . $i . ': ' . $resultError . "\n";
223                     $bFailed = true;
224                 }
225             }
226         }
227         if ($bFailed) {
228             fail('SQL errors loading placex and/or location_property_osmline tables');
229         }
230
231         for ($i = 0; $i < $this->iInstances; $i++) {
232             pg_close($aDBInstances[$i]);
233         }
234
235         echo "\n";
236         info('Reanalysing database');
237         $this->pgsqlRunScript('ANALYSE');
238
239         $sDatabaseDate = getDatabaseDate($oDB);
240         $oDB->exec('TRUNCATE import_status');
241         if (!$sDatabaseDate) {
242             warn('could not determine database date.');
243         } else {
244             $sSQL = "INSERT INTO import_status (lastimportdate) VALUES('".$sDatabaseDate."')";
245             $oDB->exec($sSQL);
246             echo "Latest data imported from $sDatabaseDate.\n";
247         }
248     }
249
250     public function importTigerData($sTigerPath)
251     {
252         info('Import Tiger data');
253
254         $aFilenames = glob($sTigerPath.'/*.sql');
255         info('Found '.count($aFilenames).' SQL files in path '.$sTigerPath);
256         if (empty($aFilenames)) {
257             warn('Tiger data import selected but no files found in path '.$sTigerPath);
258             return;
259         }
260         $sTemplate = file_get_contents(CONST_SqlDir.'/tiger_import_start.sql');
261         $sTemplate = $this->replaceSqlPatterns($sTemplate);
262
263         $this->pgsqlRunScript($sTemplate, false);
264
265         $aDBInstances = array();
266         for ($i = 0; $i < $this->iInstances; $i++) {
267             // https://secure.php.net/manual/en/function.pg-connect.php
268             $DSN = getSetting('DATABASE_DSN');
269             $DSN = preg_replace('/^pgsql:/', '', $DSN);
270             $DSN = preg_replace('/;/', ' ', $DSN);
271             $aDBInstances[$i] = pg_connect($DSN, PGSQL_CONNECT_FORCE_NEW | PGSQL_CONNECT_ASYNC);
272             pg_ping($aDBInstances[$i]);
273         }
274
275         foreach ($aFilenames as $sFile) {
276             echo $sFile.': ';
277             $hFile = fopen($sFile, 'r');
278             $sSQL = fgets($hFile, 100000);
279             $iLines = 0;
280             while (true) {
281                 for ($i = 0; $i < $this->iInstances; $i++) {
282                     if (!pg_connection_busy($aDBInstances[$i])) {
283                         while (pg_get_result($aDBInstances[$i]));
284                         $sSQL = fgets($hFile, 100000);
285                         if (!$sSQL) break 2;
286                         if (!pg_send_query($aDBInstances[$i], $sSQL)) fail(pg_last_error($aDBInstances[$i]));
287                         $iLines++;
288                         if ($iLines == 1000) {
289                             echo '.';
290                             $iLines = 0;
291                         }
292                     }
293                 }
294                 usleep(10);
295             }
296             fclose($hFile);
297
298             $bAnyBusy = true;
299             while ($bAnyBusy) {
300                 $bAnyBusy = false;
301                 for ($i = 0; $i < $this->iInstances; $i++) {
302                     if (pg_connection_busy($aDBInstances[$i])) $bAnyBusy = true;
303                 }
304                 usleep(10);
305             }
306             echo "\n";
307         }
308
309         for ($i = 0; $i < $this->iInstances; $i++) {
310             pg_close($aDBInstances[$i]);
311         }
312
313         info('Creating indexes on Tiger data');
314         $sTemplate = file_get_contents(CONST_SqlDir.'/tiger_import_finish.sql');
315         $sTemplate = $this->replaceSqlPatterns($sTemplate);
316
317         $this->pgsqlRunScript($sTemplate, false);
318     }
319
320     public function calculatePostcodes($bCMDResultAll)
321     {
322         info('Calculate Postcodes');
323         $this->pgsqlRunScriptFile(CONST_SqlDir.'/postcode_tables.sql');
324
325         $sPostcodeFilename = CONST_InstallDir.'/gb_postcode_data.sql.gz';
326         if (file_exists($sPostcodeFilename)) {
327             $this->pgsqlRunScriptFile($sPostcodeFilename);
328         } else {
329             warn('optional external GB postcode table file ('.$sPostcodeFilename.') not found. Skipping.');
330         }
331
332         $sPostcodeFilename = CONST_InstallDir.'/us_postcode_data.sql.gz';
333         if (file_exists($sPostcodeFilename)) {
334             $this->pgsqlRunScriptFile($sPostcodeFilename);
335         } else {
336             warn('optional external US postcode table file ('.$sPostcodeFilename.') not found. Skipping.');
337         }
338
339
340         $this->db()->exec('TRUNCATE location_postcode');
341
342         $sSQL  = 'INSERT INTO location_postcode';
343         $sSQL .= ' (place_id, indexed_status, country_code, postcode, geometry) ';
344         $sSQL .= "SELECT nextval('seq_place'), 1, country_code,";
345         $sSQL .= "       upper(trim (both ' ' from address->'postcode')) as pc,";
346         $sSQL .= '       ST_Centroid(ST_Collect(ST_Centroid(geometry)))';
347         $sSQL .= '  FROM placex';
348         $sSQL .= " WHERE address ? 'postcode' AND address->'postcode' NOT SIMILAR TO '%(,|;)%'";
349         $sSQL .= '       AND geometry IS NOT null';
350         $sSQL .= ' GROUP BY country_code, pc';
351         $this->db()->exec($sSQL);
352
353         // only add postcodes that are not yet available in OSM
354         $sSQL  = 'INSERT INTO location_postcode';
355         $sSQL .= ' (place_id, indexed_status, country_code, postcode, geometry) ';
356         $sSQL .= "SELECT nextval('seq_place'), 1, 'us', postcode,";
357         $sSQL .= '       ST_SetSRID(ST_Point(x,y),4326)';
358         $sSQL .= '  FROM us_postcode WHERE postcode NOT IN';
359         $sSQL .= '        (SELECT postcode FROM location_postcode';
360         $sSQL .= "          WHERE country_code = 'us')";
361         $this->db()->exec($sSQL);
362
363         // add missing postcodes for GB (if available)
364         $sSQL  = 'INSERT INTO location_postcode';
365         $sSQL .= ' (place_id, indexed_status, country_code, postcode, geometry) ';
366         $sSQL .= "SELECT nextval('seq_place'), 1, 'gb', postcode, geometry";
367         $sSQL .= '  FROM gb_postcode WHERE postcode NOT IN';
368         $sSQL .= '           (SELECT postcode FROM location_postcode';
369         $sSQL .= "             WHERE country_code = 'gb')";
370         $this->db()->exec($sSQL);
371
372         if (!$bCMDResultAll) {
373             $sSQL = "DELETE FROM word WHERE class='place' and type='postcode'";
374             $sSQL .= 'and word NOT IN (SELECT postcode FROM location_postcode)';
375             $this->db()->exec($sSQL);
376         }
377
378         $sSQL = 'SELECT count(getorcreate_postcode_id(v)) FROM ';
379         $sSQL .= '(SELECT distinct(postcode) as v FROM location_postcode) p';
380         $this->db()->exec($sSQL);
381     }
382
383     public function index($bIndexNoanalyse)
384     {
385         $this->checkModulePresence(); // raises exception on failure
386
387         $oBaseCmd = (clone $this->oNominatimCmd)->addParams('index');
388
389         info('Index ranks 0 - 4');
390         $oCmd = (clone $oBaseCmd)->addParams('--maxrank', 4);
391
392         $iStatus = $oCmd->run();
393         if ($iStatus != 0) {
394             fail('error status ' . $iStatus . ' running nominatim!');
395         }
396         if (!$bIndexNoanalyse) $this->pgsqlRunScript('ANALYSE');
397
398         info('Index administrative boundaries');
399         $oCmd = (clone $oBaseCmd)->addParams('--boundaries-only');
400         $iStatus = $oCmd->run();
401         if ($iStatus != 0) {
402             fail('error status ' . $iStatus . ' running nominatim!');
403         }
404
405         info('Index ranks 5 - 25');
406         $oCmd = (clone $oBaseCmd)->addParams('--no-boundaries', '--minrank', 5, '--maxrank', 25);
407         $iStatus = $oCmd->run();
408         if ($iStatus != 0) {
409             fail('error status ' . $iStatus . ' running nominatim!');
410         }
411
412         if (!$bIndexNoanalyse) $this->pgsqlRunScript('ANALYSE');
413
414         info('Index ranks 26 - 30');
415         $oCmd = (clone $oBaseCmd)->addParams('--no-boundaries', '--minrank', 26);
416         $iStatus = $oCmd->run();
417         if ($iStatus != 0) {
418             fail('error status ' . $iStatus . ' running nominatim!');
419         }
420
421         info('Index postcodes');
422         $sSQL = 'UPDATE location_postcode SET indexed_status = 0';
423         $this->db()->exec($sSQL);
424     }
425
426     public function createSearchIndices()
427     {
428         info('Create Search indices');
429
430         $sSQL = 'SELECT relname FROM pg_class, pg_index ';
431         $sSQL .= 'WHERE pg_index.indisvalid = false AND pg_index.indexrelid = pg_class.oid';
432         $aInvalidIndices = $this->db()->getCol($sSQL);
433
434         foreach ($aInvalidIndices as $sIndexName) {
435             info("Cleaning up invalid index $sIndexName");
436             $this->db()->exec("DROP INDEX $sIndexName;");
437         }
438
439         $sTemplate = file_get_contents(CONST_SqlDir.'/indices.src.sql');
440         if (!$this->bDrop) {
441             $sTemplate .= file_get_contents(CONST_SqlDir.'/indices_updates.src.sql');
442         }
443         if (!$this->dbReverseOnly()) {
444             $sTemplate .= file_get_contents(CONST_SqlDir.'/indices_search.src.sql');
445         }
446         $sTemplate = $this->replaceSqlPatterns($sTemplate);
447
448         $this->pgsqlRunScript($sTemplate);
449     }
450
451     public function createCountryNames()
452     {
453         info('Create search index for default country names');
454
455         $this->pgsqlRunScript("select getorcreate_country(make_standard_name('uk'), 'gb')");
456         $this->pgsqlRunScript("select getorcreate_country(make_standard_name('united states'), 'us')");
457         $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');
458         $this->pgsqlRunScript("select count(*) from (select getorcreate_country(make_standard_name(name->'name'), country_code) from country_name where name ? 'name') as x");
459         $sSQL = 'select count(*) from (select getorcreate_country(make_standard_name(v),'
460             .'country_code) from (select country_code, skeys(name) as k, svals(name) as v from country_name) x where k ';
461         $sLanguages = getSetting('LANGUAGES');
462         if ($sLanguages) {
463             $sSQL .= 'in ';
464             $sDelim = '(';
465             foreach (explode(',', $sLanguages) as $sLang) {
466                 $sSQL .= $sDelim."'name:$sLang'";
467                 $sDelim = ',';
468             }
469             $sSQL .= ')';
470         } else {
471             // all include all simple name tags
472             $sSQL .= "like 'name:%'";
473         }
474         $sSQL .= ') v';
475         $this->pgsqlRunScript($sSQL);
476     }
477
478     /**
479      * Return the connection to the database.
480      *
481      * @return Database object.
482      *
483      * Creates a new connection if none exists yet. Otherwise reuses the
484      * already established connection.
485      */
486     private function db()
487     {
488         if (is_null($this->oDB)) {
489             $this->oDB = new \Nominatim\DB();
490             $this->oDB->connect();
491         }
492
493         return $this->oDB;
494     }
495
496     private function pgsqlRunScript($sScript, $bfatal = true)
497     {
498         runSQLScript(
499             $sScript,
500             $bfatal,
501             $this->bVerbose,
502             $this->sIgnoreErrors
503         );
504     }
505
506     private function createSqlFunctions()
507     {
508         $oCmd = (clone($this->oNominatimCmd))
509                 ->addParams('refresh', '--functions');
510
511         if (!$this->bEnableDiffUpdates) {
512             $oCmd->addParams('--no-diff-updates');
513         }
514
515         if ($this->bEnableDebugStatements) {
516             $oCmd->addParams('--enable-debug-statements');
517         }
518
519         $oCmd->run(!$this->sIgnoreErrors);
520     }
521
522     private function pgsqlRunPartitionScript($sTemplate)
523     {
524         $sSQL = 'select distinct partition from country_name';
525         $aPartitions = $this->db()->getCol($sSQL);
526         if (!$this->bNoPartitions) $aPartitions[] = 0;
527
528         preg_match_all('#^-- start(.*?)^-- end#ms', $sTemplate, $aMatches, PREG_SET_ORDER);
529         foreach ($aMatches as $aMatch) {
530             $sResult = '';
531             foreach ($aPartitions as $sPartitionName) {
532                 $sResult .= str_replace('-partition-', $sPartitionName, $aMatch[1]);
533             }
534             $sTemplate = str_replace($aMatch[0], $sResult, $sTemplate);
535         }
536
537         $this->pgsqlRunScript($sTemplate);
538     }
539
540     private function pgsqlRunScriptFile($sFilename)
541     {
542         if (!file_exists($sFilename)) fail('unable to find '.$sFilename);
543
544         $oCmd = (new \Nominatim\Shell('psql'))
545                 ->addParams('--port', $this->aDSNInfo['port'])
546                 ->addParams('--dbname', $this->aDSNInfo['database']);
547
548         if (!$this->bVerbose) {
549             $oCmd->addParams('--quiet');
550         }
551         if (isset($this->aDSNInfo['hostspec'])) {
552             $oCmd->addParams('--host', $this->aDSNInfo['hostspec']);
553         }
554         if (isset($this->aDSNInfo['username'])) {
555             $oCmd->addParams('--username', $this->aDSNInfo['username']);
556         }
557         if (isset($this->aDSNInfo['password'])) {
558             $oCmd->addEnvPair('PGPASSWORD', $this->aDSNInfo['password']);
559         }
560         $ahGzipPipes = null;
561         if (preg_match('/\\.gz$/', $sFilename)) {
562             $aDescriptors = array(
563                              0 => array('pipe', 'r'),
564                              1 => array('pipe', 'w'),
565                              2 => array('file', '/dev/null', 'a')
566                             );
567             $oZcatCmd = new \Nominatim\Shell('zcat', $sFilename);
568
569             $hGzipProcess = proc_open($oZcatCmd->escapedCmd(), $aDescriptors, $ahGzipPipes);
570             if (!is_resource($hGzipProcess)) fail('unable to start zcat');
571             $aReadPipe = $ahGzipPipes[1];
572             fclose($ahGzipPipes[0]);
573         } else {
574             $oCmd->addParams('--file', $sFilename);
575             $aReadPipe = array('pipe', 'r');
576         }
577         $aDescriptors = array(
578                          0 => $aReadPipe,
579                          1 => array('pipe', 'w'),
580                          2 => array('file', '/dev/null', 'a')
581                         );
582         $ahPipes = null;
583
584         $hProcess = proc_open($oCmd->escapedCmd(), $aDescriptors, $ahPipes, null, $oCmd->aEnv);
585         if (!is_resource($hProcess)) fail('unable to start pgsql');
586         // TODO: error checking
587         while (!feof($ahPipes[1])) {
588             echo fread($ahPipes[1], 4096);
589         }
590         fclose($ahPipes[1]);
591         $iReturn = proc_close($hProcess);
592         if ($iReturn > 0) {
593             fail("pgsql returned with error code ($iReturn)");
594         }
595         if ($ahGzipPipes) {
596             fclose($ahGzipPipes[1]);
597             proc_close($hGzipProcess);
598         }
599     }
600
601     private function replaceSqlPatterns($sSql)
602     {
603         $sSql = str_replace('{www-user}', getSetting('DATABASE_WEBUSER'), $sSql);
604
605         $aPatterns = array(
606                       '{ts:address-data}' => getSetting('TABLESPACE_ADDRESS_DATA'),
607                       '{ts:address-index}' => getSetting('TABLESPACE_ADDRESS_INDEX'),
608                       '{ts:search-data}' => getSetting('TABLESPACE_SEARCH_DATA'),
609                       '{ts:search-index}' =>  getSetting('TABLESPACE_SEARCH_INDEX'),
610                       '{ts:aux-data}' =>  getSetting('TABLESPACE_AUX_DATA'),
611                       '{ts:aux-index}' =>  getSetting('TABLESPACE_AUX_INDEX')
612         );
613
614         foreach ($aPatterns as $sPattern => $sTablespace) {
615             if ($sTablespace) {
616                 $sSql = str_replace($sPattern, 'TABLESPACE "'.$sTablespace.'"', $sSql);
617             } else {
618                 $sSql = str_replace($sPattern, '', $sSql);
619             }
620         }
621
622         return $sSql;
623     }
624
625     /**
626      * Drop table with the given name if it exists.
627      *
628      * @param string $sName Name of table to remove.
629      *
630      * @return null
631      */
632     private function dropTable($sName)
633     {
634         if ($this->bVerbose) echo "Dropping table $sName\n";
635         $this->db()->deleteTable($sName);
636     }
637
638     /**
639      * Check if the database is in reverse-only mode.
640      *
641      * @return True if there is no search_name table and infrastructure.
642      */
643     private function dbReverseOnly()
644     {
645         return !($this->db()->tableExists('search_name'));
646     }
647
648     /**
649      * Try accessing the C module, so we know early if something is wrong.
650      *
651      * Raises Nominatim\DatabaseError on failure
652      */
653     private function checkModulePresence()
654     {
655         $sModulePath = getSetting('DATABASE_MODULE_PATH', CONST_InstallDir.'/module');
656         $sSQL = "CREATE FUNCTION nominatim_test_import_func(text) RETURNS text AS '";
657         $sSQL .= $sModulePath . "/nominatim.so', 'transliteration' LANGUAGE c IMMUTABLE STRICT";
658         $sSQL .= ';DROP FUNCTION nominatim_test_import_func(text);';
659
660         $oDB = new \Nominatim\DB();
661         $oDB->connect();
662         $oDB->exec($sSQL, null, 'Database server failed to load '.$sModulePath.'/nominatim.so module');
663     }
664 }