]> git.openstreetmap.org Git - nominatim.git/blob - lib/DB.php
Nominatim::DB support input variables, custom error messages
[nominatim.git] / lib / DB.php
1 <?php
2
3 namespace Nominatim;
4
5 require_once(CONST_BasePath.'/lib/DatabaseError.php');
6
7 /**
8  * Uses PDO to access the database specified in the CONST_Database_DSN
9  * setting.
10  */
11 class DB
12 {
13     protected $connection;
14
15     public function __construct($sDSN = CONST_Database_DSN)
16     {
17         $this->sDSN = $sDSN;
18     }
19
20     public function connect($bNew = false, $bPersistent = true)
21     {
22         if (isset($this->connection) && !$bNew) {
23             return true;
24         }
25         $aConnOptions = array(
26                          \PDO::ATTR_ERRMODE            => \PDO::ERRMODE_EXCEPTION,
27                          \PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_ASSOC,
28                          \PDO::ATTR_PERSISTENT         => $bPersistent
29         );
30
31         // https://secure.php.net/manual/en/ref.pdo-pgsql.connection.php
32         try {
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());
37         }
38
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
43
44         $this->connection = $conn;
45         return true;
46     }
47
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')
51     {
52         $val = null;
53         try {
54             if (isset($aInputVars)) {
55                 $stmt = $this->connection->prepare($sSQL);
56                 $stmt->execute($aInputVars);
57             } else {
58                 $val = $this->connection->exec($sSQL);
59             }
60         } catch (\PDOException $e) {
61             $sErrMessage = $e->message();
62             throw new \Nominatim\DatabaseError($sErrMessage, 500, null, $e, $sSQL);
63         }
64         return $val;
65     }
66
67     /**
68      * Executes query. Returns first row as array.
69      * Returns false if no result found.
70      *
71      * @param string  $sSQL
72      *
73      * @return array[]
74      */
75     public function getRow($sSQL, $aInputVars = null, $sErrMessage = 'Database query failed')
76     {
77         try {
78             if (isset($aInputVars)) {
79                 $stmt = $this->connection->prepare($sSQL);
80                 $stmt->execute($aInputVars);
81             } else {
82                 $stmt = $this->connection->query($sSQL);
83             }
84             $row = $stmt->fetch();
85         } catch (\PDOException $e) {
86             throw new \Nominatim\DatabaseError($sErrMessage, 500, null, $e, $sSQL);
87         }
88         return $row;
89     }
90
91     /**
92      * Executes query. Returns first value of first result.
93      * Returns false if no results found.
94      *
95      * @param string  $sSQL
96      *
97      * @return array[]
98      */
99     public function getOne($sSQL, $aInputVars = null, $sErrMessage = 'Database query failed')
100     {
101         try {
102             if (isset($aInputVars)) {
103                 $stmt = $this->connection->prepare($sSQL);
104                 $stmt->execute($aInputVars);
105             } else {
106                 $stmt = $this->connection->query($sSQL);
107             }
108             $row = $stmt->fetch(\PDO::FETCH_NUM);
109             if ($row === false) return false;
110         } catch (\PDOException $e) {
111             throw new \Nominatim\DatabaseError($sErrMessage, 500, null, $e, $sSQL);
112         }
113         return $row[0];
114     }
115
116     /**
117      * Executes query. Returns array of results (arrays).
118      * Returns empty array if no results found.
119      *
120      * @param string  $sSQL
121      *
122      * @return array[]
123      */
124     public function getAll($sSQL, $aInputVars = null, $sErrMessage = 'Database query failed')
125     {
126         try {
127             if (isset($aInputVars)) {
128                 $stmt = $this->connection->prepare($sSQL);
129                 $stmt->execute($aInputVars);
130             } else {
131                 $stmt = $this->connection->query($sSQL);
132             }
133             $rows = $stmt->fetchAll();
134         } catch (\PDOException $e) {
135             throw new \Nominatim\DatabaseError($sErrMessage, 500, null, $e, $sSQL);
136         }
137         return $rows;
138     }
139
140     /**
141      * Executes query. Returns array of the first value of each result.
142      * Returns empty array if no results found.
143      *
144      * @param string  $sSQL
145      *
146      * @return array[]
147      */
148     public function getCol($sSQL, $aInputVars = null, $sErrMessage = 'Database query failed')
149     {
150         $aVals = array();
151         try {
152             if (isset($aInputVars)) {
153                 $stmt = $this->connection->prepare($sSQL);
154                 $stmt->execute($aInputVars);
155             } else {
156                 $stmt = $this->connection->query($sSQL);
157             }
158             while ($val = $stmt->fetchColumn(0)) { // returns first column or false
159                 $aVals[] = $val;
160             }
161         } catch (\PDOException $e) {
162             throw new \Nominatim\DatabaseError($sErrMessage, 500, null, $e, $sSQL);
163         }
164         return $aVals;
165     }
166
167     /**
168      * Executes query. Returns associate array mapping first value to second value of each result.
169      * Returns empty array if no results found.
170      *
171      * @param string  $sSQL
172      *
173      * @return array[]
174      */
175     public function getAssoc($sSQL, $aInputVars = null, $sErrMessage = 'Database query failed')
176     {
177         try {
178             if (isset($aInputVars)) {
179                 $stmt = $this->connection->prepare($sSQL);
180                 $stmt->execute($aInputVars);
181             } else {
182                 $stmt = $this->connection->query($sSQL);
183             }
184             $aList = array();
185             while ($aRow = $stmt->fetch(\PDO::FETCH_NUM)) {
186                 $aList[$aRow[0]] = $aRow[1];
187             }
188         } catch (\PDOException $e) {
189             throw new \Nominatim\DatabaseError($sErrMessage, 500, null, $e, $sSQL);
190         }
191         return $aList;
192     }
193
194
195     /**
196      * St. John's Way => 'St. John\'s Way'
197      *
198      * @param string  $sVal  Text to be quoted.
199      *
200      * @return string
201      */
202     public function getDBQuoted($sVal)
203     {
204         return $this->connection->quote($sVal);
205     }
206
207     /**
208      * Like getDBQuoted, but takes an array.
209      *
210      * @param array  $aVals  List of text to be quoted.
211      *
212      * @return array[]
213      */
214     public function getDBQuotedList($aVals)
215     {
216         return array_map(function ($sVal) {
217             return $this->getDBQuoted($sVal);
218         }, $aVals);
219     }
220
221     /**
222      * [1,2,'b'] => 'ARRAY[1,2,'b']''
223      *
224      * @param array  $aVals  List of text to be quoted.
225      *
226      * @return string
227      */
228     public function getArraySQL($a)
229     {
230         return 'ARRAY['.join(',', $a).']';
231     }
232
233     public function getLastError()
234     {
235         // https://secure.php.net/manual/en/pdo.errorinfo.php
236         return $this->connection->errorInfo();
237     }
238
239     /**
240      * Check if a table exists in the database. Returns true if it does.
241      *
242      * @param string  $sTableName
243      *
244      * @return boolean
245      */
246     public function tableExists($sTableName)
247     {
248         $sSQL = 'SELECT count(*) FROM pg_tables WHERE tablename = :tablename';
249         return ($this->getOne($sSQL, array(':tablename' => $sTableName)) == 1);
250     }
251
252     /**
253      * Since the DSN includes the database name, checks if the connection works.
254      *
255      * @return boolean
256      */
257     public function databaseExists()
258     {
259         $bExists = true;
260         try {
261             $this->connect(true);
262         } catch (\Nominatim\DatabaseError $e) {
263             $bExists = false;
264         }
265         return $bExists;
266     }
267
268     /**
269      * e.g. 9.6, 10, 11.2
270      *
271      * @return float
272      */
273     public function getPostgresVersion()
274     {
275         $sVersionString = $this->getOne('SHOW server_version_num');
276         preg_match('#([0-9]?[0-9])([0-9][0-9])[0-9][0-9]#', $sVersionString, $aMatches);
277         return (float) ($aMatches[1].'.'.$aMatches[2]);
278     }
279
280     /**
281      * e.g. 2, 2.2
282      *
283      * @return float
284      */
285     public function getPostgisVersion()
286     {
287         $sVersionString = $this->getOne('select postgis_lib_version()');
288         preg_match('#^([0-9]+)[.]([0-9]+)[.]#', $sVersionString, $aMatches);
289         return (float) ($aMatches[1].'.'.$aMatches[2]);
290     }
291
292     public static function parseDSN($sDSN)
293     {
294         // https://secure.php.net/manual/en/ref.pdo-pgsql.connection.php
295         $aInfo = array();
296         if (preg_match('/^pgsql:(.+)/', $sDSN, $aMatches)) {
297             foreach (explode(';', $aMatches[1]) as $sKeyVal) {
298                 list($sKey, $sVal) = explode('=', $sKeyVal, 2);
299                 if ($sKey == 'host') $sKey = 'hostspec';
300                 if ($sKey == 'dbname') $sKey = 'database';
301                 if ($sKey == 'user') $sKey = 'username';
302                 $aInfo[$sKey] = $sVal;
303             }
304         }
305         return $aInfo;
306     }
307 }