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