5 require_once(CONST_BasePath.'/lib/DatabaseError.php');
 
   8  * Uses PDO to access the database specified in the CONST_Database_DSN
 
  13     protected $connection;
 
  15     public function __construct($sDSN = CONST_Database_DSN)
 
  20     public function connect($bNew = false, $bPersistent = true)
 
  22         if (isset($this->connection) && !$bNew) {
 
  25         $aConnOptions = array(
 
  26                          \PDO::ATTR_ERRMODE            => \PDO::ERRMODE_EXCEPTION,
 
  27                          \PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_ASSOC,
 
  28                          \PDO::ATTR_PERSISTENT         => $bPersistent
 
  31         // https://secure.php.net/manual/en/ref.pdo-pgsql.connection.php
 
  33             $conn = new \PDO($this->sDSN, null, null, $aConnOptions);
 
  34         } catch (\PDOException $e) {
 
  35             $sMsg = 'Failed to establish database connection:' . $e->getMessage();
 
  36             throw new \Nominatim\DatabaseError($sMsg, 500, null, $e->getMessage());
 
  39         $conn->exec("SET DateStyle TO 'sql,european'");
 
  40         $conn->exec("SET client_encoding TO 'utf-8'");
 
  41         $iMaxExecution = ini_get('max_execution_time');
 
  42         if ($iMaxExecution > 0) $conn->setAttribute(\PDO::ATTR_TIMEOUT, $iMaxExecution); // seconds
 
  44         $this->connection = $conn;
 
  48     // returns the number of rows that were modified or deleted by the SQL
 
  49     // statement. If no rows were affected returns 0.
 
  50     public function exec($sSQL, $aInputVars = null, $sErrMessage = 'Database query failed')
 
  54             if (isset($aInputVars)) {
 
  55                 $stmt = $this->connection->prepare($sSQL);
 
  56                 $stmt->execute($aInputVars);
 
  58                 $val = $this->connection->exec($sSQL);
 
  60         } catch (\PDOException $e) {
 
  61             throw new \Nominatim\DatabaseError($sErrMessage, 500, null, $e, $sSQL);
 
  67      * Executes query. Returns first row as array.
 
  68      * Returns false if no result found.
 
  74     public function getRow($sSQL, $aInputVars = null, $sErrMessage = 'Database query failed')
 
  77             $stmt = $this->getQueryStatement($sSQL, $aInputVars, $sErrMessage);
 
  78             $row = $stmt->fetch();
 
  79         } catch (\PDOException $e) {
 
  80             throw new \Nominatim\DatabaseError($sErrMessage, 500, null, $e, $sSQL);
 
  86      * Executes query. Returns first value of first result.
 
  87      * Returns false if no results found.
 
  93     public function getOne($sSQL, $aInputVars = null, $sErrMessage = 'Database query failed')
 
  96             $stmt = $this->getQueryStatement($sSQL, $aInputVars, $sErrMessage);
 
  97             $row = $stmt->fetch(\PDO::FETCH_NUM);
 
  98             if ($row === false) return false;
 
  99         } catch (\PDOException $e) {
 
 100             throw new \Nominatim\DatabaseError($sErrMessage, 500, null, $e, $sSQL);
 
 106      * Executes query. Returns array of results (arrays).
 
 107      * Returns empty array if no results found.
 
 109      * @param string  $sSQL
 
 113     public function getAll($sSQL, $aInputVars = null, $sErrMessage = 'Database query failed')
 
 116             $stmt = $this->getQueryStatement($sSQL, $aInputVars, $sErrMessage);
 
 117             $rows = $stmt->fetchAll();
 
 118         } catch (\PDOException $e) {
 
 119             throw new \Nominatim\DatabaseError($sErrMessage, 500, null, $e, $sSQL);
 
 125      * Executes query. Returns array of the first value of each result.
 
 126      * Returns empty array if no results found.
 
 128      * @param string  $sSQL
 
 132     public function getCol($sSQL, $aInputVars = null, $sErrMessage = 'Database query failed')
 
 136             $stmt = $this->getQueryStatement($sSQL, $aInputVars, $sErrMessage);
 
 138             while (($val = $stmt->fetchColumn(0)) !== false) { // returns first column or false
 
 141         } catch (\PDOException $e) {
 
 142             throw new \Nominatim\DatabaseError($sErrMessage, 500, null, $e, $sSQL);
 
 148      * Executes query. Returns associate array mapping first value to second value of each result.
 
 149      * Returns empty array if no results found.
 
 151      * @param string  $sSQL
 
 155     public function getAssoc($sSQL, $aInputVars = null, $sErrMessage = 'Database query failed')
 
 158             $stmt = $this->getQueryStatement($sSQL, $aInputVars, $sErrMessage);
 
 161             while ($aRow = $stmt->fetch(\PDO::FETCH_NUM)) {
 
 162                 $aList[$aRow[0]] = $aRow[1];
 
 164         } catch (\PDOException $e) {
 
 165             throw new \Nominatim\DatabaseError($sErrMessage, 500, null, $e, $sSQL);
 
 171      * Executes query. Returns a PDO statement to iterate over.
 
 173      * @param string  $sSQL
 
 175      * @return PDOStatement
 
 177     public function getQueryStatement($sSQL, $aInputVars = null, $sErrMessage = 'Database query failed')
 
 180             if (isset($aInputVars)) {
 
 181                 $stmt = $this->connection->prepare($sSQL);
 
 182                 $stmt->execute($aInputVars);
 
 184                 $stmt = $this->connection->query($sSQL);
 
 186         } catch (\PDOException $e) {
 
 187             throw new \Nominatim\DatabaseError($sErrMessage, 500, null, $e, $sSQL);
 
 193      * St. John's Way => 'St. John\'s Way'
 
 195      * @param string  $sVal  Text to be quoted.
 
 199     public function getDBQuoted($sVal)
 
 201         return $this->connection->quote($sVal);
 
 205      * Like getDBQuoted, but takes an array.
 
 207      * @param array  $aVals  List of text to be quoted.
 
 211     public function getDBQuotedList($aVals)
 
 213         return array_map(function ($sVal) {
 
 214             return $this->getDBQuoted($sVal);
 
 219      * [1,2,'b'] => 'ARRAY[1,2,'b']''
 
 221      * @param array  $aVals  List of text to be quoted.
 
 225     public function getArraySQL($a)
 
 227         return 'ARRAY['.join(',', $a).']';
 
 231      * Check if a table exists in the database. Returns true if it does.
 
 233      * @param string  $sTableName
 
 237     public function tableExists($sTableName)
 
 239         $sSQL = 'SELECT count(*) FROM pg_tables WHERE tablename = :tablename';
 
 240         return ($this->getOne($sSQL, array(':tablename' => $sTableName)) == 1);
 
 244     * Check if an index exists in the database. Optional filtered by tablename
 
 246     * @param string  $sTableName
 
 250     public function indexExists($sIndexName, $sTableName = null)
 
 252         return in_array($sIndexName, $this->getListOfIndices($sTableName));
 
 256     * Returns a list of index names in the database, optional filtered by tablename
 
 258     * @param string  $sTableName
 
 262     public function getListOfIndices($sTableName = null)
 
 264         //  table_name            | index_name                      | column_name
 
 265         // -----------------------+---------------------------------+--------------
 
 266         //  country_name          | idx_country_name_country_code   | country_code
 
 267         //  country_osm_grid      | idx_country_osm_grid_geometry   | geometry
 
 268         //  import_polygon_delete | idx_import_polygon_delete_osmid | osm_id
 
 269         //  import_polygon_delete | idx_import_polygon_delete_osmid | osm_type
 
 270         //  import_polygon_error  | idx_import_polygon_error_osmid  | osm_id
 
 271         //  import_polygon_error  | idx_import_polygon_error_osmid  | osm_type
 
 274     t.relname as table_name,
 
 275     i.relname as index_name,
 
 276     a.attname as column_name
 
 284     and i.oid = ix.indexrelid
 
 285     and a.attrelid = t.oid
 
 286     and a.attnum = ANY(ix.indkey)
 
 288     and i.relname NOT LIKE 'pg_%'
 
 298             $sSql = str_replace('FILTERS', 'and t.relname = :tablename', $sSql);
 
 299             $aRows = $this->getAll($sSql, array(':tablename' => $sTableName));
 
 301             $sSql = str_replace('FILTERS', '', $sSql);
 
 302             $aRows = $this->getAll($sSql);
 
 305         $aIndexNames = array_unique(array_map(function ($aRow) {
 
 306             return $aRow['index_name'];
 
 314      * Since the DSN includes the database name, checks if the connection works.
 
 318     public function databaseExists()
 
 322             $this->connect(true);
 
 323         } catch (\Nominatim\DatabaseError $e) {
 
 334     public function getPostgresVersion()
 
 336         $sVersionString = $this->getOne('SHOW server_version_num');
 
 337         preg_match('#([0-9]?[0-9])([0-9][0-9])[0-9][0-9]#', $sVersionString, $aMatches);
 
 338         return (float) ($aMatches[1].'.'.$aMatches[2]);
 
 346     public function getPostgisVersion()
 
 348         $sVersionString = $this->getOne('select postgis_lib_version()');
 
 349         preg_match('#^([0-9]+)[.]([0-9]+)[.]#', $sVersionString, $aMatches);
 
 350         return (float) ($aMatches[1].'.'.$aMatches[2]);
 
 353     public static function parseDSN($sDSN)
 
 355         // https://secure.php.net/manual/en/ref.pdo-pgsql.connection.php
 
 357         if (preg_match('/^pgsql:(.+)$/', $sDSN, $aMatches)) {
 
 358             foreach (explode(';', $aMatches[1]) as $sKeyVal) {
 
 359                 list($sKey, $sVal) = explode('=', $sKeyVal, 2);
 
 360                 if ($sKey == 'host') $sKey = 'hostspec';
 
 361                 if ($sKey == 'dbname') $sKey = 'database';
 
 362                 if ($sKey == 'user') $sKey = 'username';
 
 363                 $aInfo[$sKey] = $sVal;