5 require_once(CONST_LibDir.'/DatabaseError.php');
8 * Uses PDO to access the database specified in the CONST_Database_DSN
13 protected $connection;
15 public function __construct($sDSN = null)
17 $this->sDSN = $sDSN ?? getSetting('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) {
43 $conn->setAttribute(\PDO::ATTR_TIMEOUT, $iMaxExecution); // seconds
46 $this->connection = $conn;
50 // returns the number of rows that were modified or deleted by the SQL
51 // statement. If no rows were affected returns 0.
52 public function exec($sSQL, $aInputVars = null, $sErrMessage = 'Database query failed')
56 if (isset($aInputVars)) {
57 $stmt = $this->connection->prepare($sSQL);
58 $stmt->execute($aInputVars);
60 $val = $this->connection->exec($sSQL);
62 } catch (\PDOException $e) {
63 throw new \Nominatim\DatabaseError($sErrMessage, 500, null, $e, $sSQL);
69 * Executes query. Returns first row as array.
70 * Returns false if no result found.
76 public function getRow($sSQL, $aInputVars = null, $sErrMessage = 'Database query failed')
79 $stmt = $this->getQueryStatement($sSQL, $aInputVars, $sErrMessage);
80 $row = $stmt->fetch();
81 } catch (\PDOException $e) {
82 throw new \Nominatim\DatabaseError($sErrMessage, 500, null, $e, $sSQL);
88 * Executes query. Returns first value of first result.
89 * Returns false if no results found.
95 public function getOne($sSQL, $aInputVars = null, $sErrMessage = 'Database query failed')
98 $stmt = $this->getQueryStatement($sSQL, $aInputVars, $sErrMessage);
99 $row = $stmt->fetch(\PDO::FETCH_NUM);
100 if ($row === false) {
103 } catch (\PDOException $e) {
104 throw new \Nominatim\DatabaseError($sErrMessage, 500, null, $e, $sSQL);
110 * Executes query. Returns array of results (arrays).
111 * Returns empty array if no results found.
113 * @param string $sSQL
117 public function getAll($sSQL, $aInputVars = null, $sErrMessage = 'Database query failed')
120 $stmt = $this->getQueryStatement($sSQL, $aInputVars, $sErrMessage);
121 $rows = $stmt->fetchAll();
122 } catch (\PDOException $e) {
123 throw new \Nominatim\DatabaseError($sErrMessage, 500, null, $e, $sSQL);
129 * Executes query. Returns array of the first value of each result.
130 * Returns empty array if no results found.
132 * @param string $sSQL
136 public function getCol($sSQL, $aInputVars = null, $sErrMessage = 'Database query failed')
140 $stmt = $this->getQueryStatement($sSQL, $aInputVars, $sErrMessage);
142 while (($val = $stmt->fetchColumn(0)) !== false) { // returns first column or false
145 } catch (\PDOException $e) {
146 throw new \Nominatim\DatabaseError($sErrMessage, 500, null, $e, $sSQL);
152 * Executes query. Returns associate array mapping first value to second value of each result.
153 * Returns empty array if no results found.
155 * @param string $sSQL
159 public function getAssoc($sSQL, $aInputVars = null, $sErrMessage = 'Database query failed')
162 $stmt = $this->getQueryStatement($sSQL, $aInputVars, $sErrMessage);
165 while ($aRow = $stmt->fetch(\PDO::FETCH_NUM)) {
166 $aList[$aRow[0]] = $aRow[1];
168 } catch (\PDOException $e) {
169 throw new \Nominatim\DatabaseError($sErrMessage, 500, null, $e, $sSQL);
175 * Executes query. Returns a PDO statement to iterate over.
177 * @param string $sSQL
179 * @return PDOStatement
181 public function getQueryStatement($sSQL, $aInputVars = null, $sErrMessage = 'Database query failed')
184 if (isset($aInputVars)) {
185 $stmt = $this->connection->prepare($sSQL);
186 $stmt->execute($aInputVars);
188 $stmt = $this->connection->query($sSQL);
190 } catch (\PDOException $e) {
191 throw new \Nominatim\DatabaseError($sErrMessage, 500, null, $e, $sSQL);
197 * St. John's Way => 'St. John\'s Way'
199 * @param string $sVal Text to be quoted.
203 public function getDBQuoted($sVal)
205 return $this->connection->quote($sVal);
209 * Like getDBQuoted, but takes an array.
211 * @param array $aVals List of text to be quoted.
215 public function getDBQuotedList($aVals)
217 return array_map(function ($sVal) {
218 return $this->getDBQuoted($sVal);
223 * [1,2,'b'] => 'ARRAY[1,2,'b']''
225 * @param array $aVals List of text to be quoted.
229 public function getArraySQL($a)
231 return 'ARRAY['.join(',', $a).']';
235 * Check if a table exists in the database. Returns true if it does.
237 * @param string $sTableName
241 public function tableExists($sTableName)
243 $sSQL = 'SELECT count(*) FROM pg_tables WHERE tablename = :tablename';
244 return ($this->getOne($sSQL, array(':tablename' => $sTableName)) == 1);
248 * Deletes a table. Returns true if deleted or didn't exist.
250 * @param string $sTableName
254 public function deleteTable($sTableName)
256 return $this->exec('DROP TABLE IF EXISTS '.$sTableName.' CASCADE') == 0;
260 * Tries to connect to the database but on failure doesn't throw an exception.
264 public function checkConnection()
268 $this->connect(true);
269 } catch (\Nominatim\DatabaseError $e) {
280 public function getPostgresVersion()
282 $sVersionString = $this->getOne('SHOW server_version_num');
283 preg_match('#([0-9]?[0-9])([0-9][0-9])[0-9][0-9]#', $sVersionString, $aMatches);
284 return (float) ($aMatches[1].'.'.$aMatches[2]);
292 public function getPostgisVersion()
294 $sVersionString = $this->getOne('select postgis_lib_version()');
295 preg_match('#^([0-9]+)[.]([0-9]+)[.]#', $sVersionString, $aMatches);
296 return (float) ($aMatches[1].'.'.$aMatches[2]);
300 * Returns an associate array of postgresql database connection settings. Keys can
301 * be 'database', 'hostspec', 'port', 'username', 'password'.
302 * Returns empty array on failure, thus check if at least 'database' is set.
306 public static function parseDSN($sDSN)
308 // https://secure.php.net/manual/en/ref.pdo-pgsql.connection.php
310 if (preg_match('/^pgsql:(.+)$/', $sDSN, $aMatches)) {
311 foreach (explode(';', $aMatches[1]) as $sKeyVal) {
312 list($sKey, $sVal) = explode('=', $sKeyVal, 2);
313 if ($sKey == 'host') {
315 } elseif ($sKey == 'dbname') {
317 } elseif ($sKey == 'user') {
320 $aInfo[$sKey] = $sVal;
327 * Takes an array of settings and return the DNS string. Key names can be
328 * 'database', 'hostspec', 'port', 'username', 'password' but aliases
329 * 'dbname', 'host' and 'user' are also supported.
334 public static function generateDSN($aInfo)
337 'pgsql:host=%s;port=%s;dbname=%s;user=%s;password=%s;',
338 $aInfo['host'] ?? $aInfo['hostspec'] ?? '',
339 $aInfo['port'] ?? '',
340 $aInfo['dbname'] ?? $aInfo['database'] ?? '',
341 $aInfo['user'] ?? '',
342 $aInfo['password'] ?? ''
344 $sDSN = preg_replace('/\b\w+=;/', '', $sDSN);
345 $sDSN = preg_replace('/;\Z/', '', $sDSN);