]> git.mxchange.org Git - friendica.git/blob - src/Database/DBA.php
Merge pull request #7417 from nupplaphil/task/app_dependencies
[friendica.git] / src / Database / DBA.php
1 <?php
2
3 namespace Friendica\Database;
4
5 use Friendica\BaseObject;
6 use mysqli;
7 use mysqli_result;
8 use mysqli_stmt;
9 use PDO;
10 use PDOStatement;
11
12 /**
13  * @class MySQL database class
14  *
15  * This class is for the low level database stuff that does driver specific things.
16  */
17 class DBA extends BaseObject
18 {
19         /**
20          * Lowest possible date value
21          */
22         const NULL_DATE     = '0001-01-01';
23         /**
24          * Lowest possible datetime value
25          */
26         const NULL_DATETIME = '0001-01-01 00:00:00';
27
28         public static function connect()
29         {
30                 return self::getClass(Database::class)->connect();
31         }
32
33         /**
34          * Disconnects the current database connection
35          */
36         public static function disconnect()
37         {
38                 self::getClass(Database::class)->disconnect();
39         }
40
41         /**
42          * Perform a reconnect of an existing database connection
43          */
44         public static function reconnect()
45         {
46                 return self::getClass(Database::class)->reconnect();
47         }
48
49         /**
50          * Return the database object.
51          * @return PDO|mysqli
52          */
53         public static function getConnection()
54         {
55                 return self::getClass(Database::class)->getConnection();
56         }
57
58         /**
59          * @brief Returns the MySQL server version string
60          *
61          * This function discriminate between the deprecated mysql API and the current
62          * object-oriented mysqli API. Example of returned string: 5.5.46-0+deb8u1
63          *
64          * @return string
65          */
66         public static function serverInfo()
67         {
68                 return self::getClass(Database::class)->serverInfo();
69         }
70
71         /**
72          * @brief Returns the selected database name
73          *
74          * @return string
75          * @throws \Exception
76          */
77         public static function databaseName()
78         {
79                 return self::getClass(Database::class)->databaseName();
80         }
81
82         public static function escape($str)
83         {
84                 return self::getClass(Database::class)->escape($str);
85         }
86
87         public static function connected()
88         {
89                 return self::getClass(Database::class)->connected();
90         }
91
92         /**
93          * @brief Replaces ANY_VALUE() function by MIN() function,
94          *  if the database server does not support ANY_VALUE().
95          *
96          * Considerations for Standard SQL, or MySQL with ONLY_FULL_GROUP_BY (default since 5.7.5).
97          * ANY_VALUE() is available from MySQL 5.7.5 https://dev.mysql.com/doc/refman/5.7/en/miscellaneous-functions.html
98          * A standard fall-back is to use MIN().
99          *
100          * @param string $sql An SQL string without the values
101          * @return string The input SQL string modified if necessary.
102          */
103         public static function anyValueFallback($sql)
104         {
105                 return self::getClass(Database::class)->anyValueFallback($sql);
106         }
107
108         /**
109          * @brief beautifies the query - useful for "SHOW PROCESSLIST"
110          *
111          * This is safe when we bind the parameters later.
112          * The parameter values aren't part of the SQL.
113          *
114          * @param string $sql An SQL string without the values
115          * @return string The input SQL string modified if necessary.
116          */
117         public static function cleanQuery($sql)
118         {
119                 $search = ["\t", "\n", "\r", "  "];
120                 $replace = [' ', ' ', ' ', ' '];
121                 do {
122                         $oldsql = $sql;
123                         $sql = str_replace($search, $replace, $sql);
124                 } while ($oldsql != $sql);
125
126                 return $sql;
127         }
128
129         /**
130          * @brief Convert parameter array to an universal form
131          * @param array $args Parameter array
132          * @return array universalized parameter array
133          */
134         public static function getParam($args)
135         {
136                 unset($args[0]);
137
138                 // When the second function parameter is an array then use this as the parameter array
139                 if ((count($args) > 0) && (is_array($args[1]))) {
140                         return $args[1];
141                 } else {
142                         return $args;
143                 }
144         }
145
146         /**
147          * @brief Executes a prepared statement that returns data
148          * @usage Example: $r = p("SELECT * FROM `item` WHERE `guid` = ?", $guid);
149          *
150          * Please only use it with complicated queries.
151          * For all regular queries please use DBA::select or DBA::exists
152          *
153          * @param string $sql SQL statement
154          * @return bool|object statement object or result object
155          * @throws \Exception
156          */
157         public static function p($sql)
158         {
159                 $params = self::getParam(func_get_args());
160
161                 return self::getClass(Database::class)->p($sql, $params);
162         }
163
164         /**
165          * @brief Executes a prepared statement like UPDATE or INSERT that doesn't return data
166          *
167          * Please use DBA::delete, DBA::insert, DBA::update, ... instead
168          *
169          * @param string $sql SQL statement
170          * @return boolean Was the query successfull? False is returned only if an error occurred
171          * @throws \Exception
172          */
173         public static function e($sql) {
174
175                 $params = self::getParam(func_get_args());
176
177                 return self::getClass(Database::class)->e($sql, $params);
178         }
179
180         /**
181          * @brief Check if data exists
182          *
183          * @param string $table     Table name
184          * @param array  $condition array of fields for condition
185          *
186          * @return boolean Are there rows for that condition?
187          * @throws \Exception
188          */
189         public static function exists($table, $condition)
190         {
191                 return self::getClass(Database::class)->exists($table, $condition);
192         }
193
194         /**
195          * Fetches the first row
196          *
197          * Please use DBA::selectFirst or DBA::exists whenever this is possible.
198          *
199          * @brief Fetches the first row
200          * @param string $sql SQL statement
201          * @return array first row of query
202          * @throws \Exception
203          */
204         public static function fetchFirst($sql)
205         {
206                 $params = self::getParam(func_get_args());
207
208                 return self::getClass(Database::class)->fetchFirst($sql, $params);
209         }
210
211         /**
212          * @brief Returns the number of affected rows of the last statement
213          *
214          * @return int Number of rows
215          */
216         public static function affectedRows()
217         {
218                 return self::getClass(Database::class)->affectedRows();
219         }
220
221         /**
222          * @brief Returns the number of columns of a statement
223          *
224          * @param object Statement object
225          * @return int Number of columns
226          */
227         public static function columnCount($stmt)
228         {
229                 return self::getClass(Database::class)->columnCount($stmt);
230         }
231         /**
232          * @brief Returns the number of rows of a statement
233          *
234          * @param PDOStatement|mysqli_result|mysqli_stmt Statement object
235          * @return int Number of rows
236          */
237         public static function numRows($stmt)
238         {
239                 return self::getClass(Database::class)->numRows($stmt);
240         }
241
242         /**
243          * @brief Fetch a single row
244          *
245          * @param mixed $stmt statement object
246          * @return array current row
247          */
248         public static function fetch($stmt)
249         {
250                 return self::getClass(Database::class)->fetch($stmt);
251         }
252
253         /**
254          * @brief Insert a row into a table
255          *
256          * @param string $table               Table name
257          * @param array  $param               parameter array
258          * @param bool   $on_duplicate_update Do an update on a duplicate entry
259          *
260          * @return boolean was the insert successful?
261          * @throws \Exception
262          */
263         public static function insert($table, $param, $on_duplicate_update = false)
264         {
265                 return self::getClass(Database::class)->insert($table, $param, $on_duplicate_update);
266         }
267
268         /**
269          * @brief Fetch the id of the last insert command
270          *
271          * @return integer Last inserted id
272          */
273         public static function lastInsertId()
274         {
275                 return self::getClass(Database::class)->lastInsertId();
276         }
277
278         /**
279          * @brief Locks a table for exclusive write access
280          *
281          * This function can be extended in the future to accept a table array as well.
282          *
283          * @param string $table Table name
284          *
285          * @return boolean was the lock successful?
286          * @throws \Exception
287          */
288         public static function lock($table)
289         {
290                 return self::getClass(Database::class)->lock($table);
291         }
292
293         /**
294          * @brief Unlocks all locked tables
295          *
296          * @return boolean was the unlock successful?
297          * @throws \Exception
298          */
299         public static function unlock()
300         {
301                 return self::getClass(Database::class)->unlock();
302         }
303
304         /**
305          * @brief Starts a transaction
306          *
307          * @return boolean Was the command executed successfully?
308          */
309         public static function transaction()
310         {
311                 return self::getClass(Database::class)->transaction();
312         }
313
314         /**
315          * @brief Does a commit
316          *
317          * @return boolean Was the command executed successfully?
318          */
319         public static function commit()
320         {
321                 return self::getClass(Database::class)->commit();
322         }
323
324         /**
325          * @brief Does a rollback
326          *
327          * @return boolean Was the command executed successfully?
328          */
329         public static function rollback()
330         {
331                 return self::getClass(Database::class)->rollback();
332         }
333
334         /**
335          * @brief Delete a row from a table
336          *
337          * @param string $table      Table name
338          * @param array  $conditions Field condition(s)
339          * @param array  $options
340          *                           - cascade: If true we delete records in other tables that depend on the one we're deleting through
341          *                           relations (default: true)
342          *
343          * @return boolean was the delete successful?
344          * @throws \Exception
345          */
346         public static function delete($table, array $conditions, array $options = [])
347         {
348                 return self::getClass(Database::class)->delete($table, $conditions, $options);
349         }
350
351         /**
352          * @brief Updates rows
353          *
354          * Updates rows in the database. When $old_fields is set to an array,
355          * the system will only do an update if the fields in that array changed.
356          *
357          * Attention:
358          * Only the values in $old_fields are compared.
359          * This is an intentional behaviour.
360          *
361          * Example:
362          * We include the timestamp field in $fields but not in $old_fields.
363          * Then the row will only get the new timestamp when the other fields had changed.
364          *
365          * When $old_fields is set to a boolean value the system will do this compare itself.
366          * When $old_fields is set to "true" the system will do an insert if the row doesn't exists.
367          *
368          * Attention:
369          * Only set $old_fields to a boolean value when you are sure that you will update a single row.
370          * When you set $old_fields to "true" then $fields must contain all relevant fields!
371          *
372          * @param string        $table      Table name
373          * @param array         $fields     contains the fields that are updated
374          * @param array         $condition  condition array with the key values
375          * @param array|boolean $old_fields array with the old field values that are about to be replaced (true = update on duplicate)
376          *
377          * @return boolean was the update successfull?
378          * @throws \Exception
379          */
380         public static function update($table, $fields, $condition, $old_fields = [])
381         {
382                 return self::getClass(Database::class)->update($table, $fields, $condition, $old_fields);
383         }
384
385         /**
386          * Retrieve a single record from a table and returns it in an associative array
387          *
388          * @brief Retrieve a single record from a table
389          * @param string $table
390          * @param array  $fields
391          * @param array  $condition
392          * @param array  $params
393          * @return bool|array
394          * @throws \Exception
395          * @see   self::select
396          */
397         public static function selectFirst($table, array $fields = [], array $condition = [], $params = [])
398         {
399                 return self::getClass(Database::class)->selectFirst($table, $fields, $condition, $params);
400         }
401
402         /**
403          * @brief Select rows from a table and fills an array with the data
404          *
405          * @param string $table     Table name
406          * @param array  $fields    Array of selected fields, empty for all
407          * @param array  $condition Array of fields for condition
408          * @param array  $params    Array of several parameters
409          *
410          * @return array Data array
411          * @throws \Exception
412          * @see   self::select
413          */
414         public static function selectToArray(string $table, array $fields = [], array $condition = [], array $params = [])
415         {
416                 return self::getClass(Database::class)->selectToArray($table, $fields, $condition, $params);
417         }
418
419         /**
420          * @brief Select rows from a table
421          *
422          * @param string $table     Table name
423          * @param array  $fields    Array of selected fields, empty for all
424          * @param array  $condition Array of fields for condition
425          * @param array  $params    Array of several parameters
426          *
427          * @return boolean|object
428          *
429          * Example:
430          * $table = "item";
431          * $fields = array("id", "uri", "uid", "network");
432          *
433          * $condition = array("uid" => 1, "network" => 'dspr');
434          * or:
435          * $condition = array("`uid` = ? AND `network` IN (?, ?)", 1, 'dfrn', 'dspr');
436          *
437          * $params = array("order" => array("id", "received" => true), "limit" => 10);
438          *
439          * $data = DBA::select($table, $fields, $condition, $params);
440          * @throws \Exception
441          */
442         public static function select($table, array $fields = [], array $condition = [], array $params = [])
443         {
444                 return self::getClass(Database::class)->select($table, $fields, $condition, $params);
445         }
446
447         /**
448          * @brief Counts the rows from a table satisfying the provided condition
449          *
450          * @param string $table     Table name
451          * @param array  $condition array of fields for condition
452          *
453          * @return int
454          *
455          * Example:
456          * $table = "item";
457          *
458          * $condition = ["uid" => 1, "network" => 'dspr'];
459          * or:
460          * $condition = ["`uid` = ? AND `network` IN (?, ?)", 1, 'dfrn', 'dspr'];
461          *
462          * $count = DBA::count($table, $condition);
463          * @throws \Exception
464          */
465         public static function count($table, array $condition = [])
466         {
467                 return self::getClass(Database::class)->count($table, $condition);
468         }
469
470         /**
471          * @brief Returns the SQL condition string built from the provided condition array
472          *
473          * This function operates with two modes.
474          * - Supplied with a filed/value associative array, it builds simple strict
475          *   equality conditions linked by AND.
476          * - Supplied with a flat list, the first element is the condition string and
477          *   the following arguments are the values to be interpolated
478          *
479          * $condition = ["uid" => 1, "network" => 'dspr'];
480          * or:
481          * $condition = ["`uid` = ? AND `network` IN (?, ?)", 1, 'dfrn', 'dspr'];
482          *
483          * In either case, the provided array is left with the parameters only
484          *
485          * @param array $condition
486          * @return string
487          */
488         public static function buildCondition(array &$condition = [])
489         {
490                 $condition_string = '';
491                 if (count($condition) > 0) {
492                         reset($condition);
493                         $first_key = key($condition);
494                         if (is_int($first_key)) {
495                                 $condition_string = " WHERE (" . array_shift($condition) . ")";
496                         } else {
497                                 $new_values = [];
498                                 $condition_string = "";
499                                 foreach ($condition as $field => $value) {
500                                         if ($condition_string != "") {
501                                                 $condition_string .= " AND ";
502                                         }
503                                         if (is_array($value)) {
504                                                 /* Workaround for MySQL Bug #64791.
505                                                  * Never mix data types inside any IN() condition.
506                                                  * In case of mixed types, cast all as string.
507                                                  * Logic needs to be consistent with DBA::p() data types.
508                                                  */
509                                                 $is_int = false;
510                                                 $is_alpha = false;
511                                                 foreach ($value as $single_value) {
512                                                         if (is_int($single_value)) {
513                                                                 $is_int = true;
514                                                         } else {
515                                                                 $is_alpha = true;
516                                                         }
517                                                 }
518
519                                                 if ($is_int && $is_alpha) {
520                                                         foreach ($value as &$ref) {
521                                                                 if (is_int($ref)) {
522                                                                         $ref = (string)$ref;
523                                                                 }
524                                                         }
525                                                         unset($ref); //Prevent accidental re-use.
526                                                 }
527
528                                                 $new_values = array_merge($new_values, array_values($value));
529                                                 $placeholders = substr(str_repeat("?, ", count($value)), 0, -2);
530                                                 $condition_string .= "`" . $field . "` IN (" . $placeholders . ")";
531                                         } elseif (is_null($value)) {
532                                                 $condition_string .= "`" . $field . "` IS NULL";
533                                         } else {
534                                                 $new_values[$field] = $value;
535                                                 $condition_string .= "`" . $field . "` = ?";
536                                         }
537                                 }
538                                 $condition_string = " WHERE (" . $condition_string . ")";
539                                 $condition = $new_values;
540                         }
541                 }
542
543                 return $condition_string;
544         }
545
546         /**
547          * @brief Returns the SQL parameter string built from the provided parameter array
548          *
549          * @param array $params
550          * @return string
551          */
552         public static function buildParameter(array $params = [])
553         {
554                 $groupby_string = '';
555                 if (isset($params['group_by'])) {
556                         $groupby_string = " GROUP BY ";
557                         foreach ($params['group_by'] as $fields) {
558                                 $groupby_string .= "`" . $fields . "`, ";
559                         }
560                         $groupby_string = substr($groupby_string, 0, -2);
561                 }
562
563                 $order_string = '';
564                 if (isset($params['order'])) {
565                         $order_string = " ORDER BY ";
566                         foreach ($params['order'] AS $fields => $order) {
567                                 if ($order === 'RAND()') {
568                                         $order_string .= "RAND(), ";
569                                 } elseif (!is_int($fields)) {
570                                         $order_string .= "`" . $fields . "` " . ($order ? "DESC" : "ASC") . ", ";
571                                 } else {
572                                         $order_string .= "`" . $order . "`, ";
573                                 }
574                         }
575                         $order_string = substr($order_string, 0, -2);
576                 }
577
578                 $limit_string = '';
579                 if (isset($params['limit']) && is_numeric($params['limit'])) {
580                         $limit_string = " LIMIT " . intval($params['limit']);
581                 }
582
583                 if (isset($params['limit']) && is_array($params['limit'])) {
584                         $limit_string = " LIMIT " . intval($params['limit'][0]) . ", " . intval($params['limit'][1]);
585                 }
586
587                 return $groupby_string . $order_string . $limit_string;
588         }
589
590         /**
591          * @brief Fills an array with data from a query
592          *
593          * @param object $stmt statement object
594          * @param bool   $do_close
595          * @return array Data array
596          */
597         public static function toArray($stmt, $do_close = true)
598         {
599                 return self::getClass(Database::class)->toArray($stmt, $do_close);
600         }
601
602         /**
603          * @brief Returns the error number of the last query
604          *
605          * @return string Error number (0 if no error)
606          */
607         public static function errorNo()
608         {
609                 return self::getClass(Database::class)->errorNo();
610         }
611
612         /**
613          * @brief Returns the error message of the last query
614          *
615          * @return string Error message ('' if no error)
616          */
617         public static function errorMessage()
618         {
619                 return self::getClass(Database::class)->errorMessage();
620         }
621
622         /**
623          * @brief Closes the current statement
624          *
625          * @param object $stmt statement object
626          * @return boolean was the close successful?
627          */
628         public static function close($stmt)
629         {
630                 return self::getClass(Database::class)->close($stmt);
631         }
632
633         /**
634          * @brief Return a list of database processes
635          *
636          * @return array
637          *      'list' => List of processes, separated in their different states
638          *      'amount' => Number of concurrent database processes
639          * @throws \Exception
640          */
641         public static function processlist()
642         {
643                 return self::getClass(Database::class)->processlist();
644         }
645
646         /**
647          * Checks if $array is a filled array with at least one entry.
648          *
649          * @param mixed $array A filled array with at least one entry
650          *
651          * @return boolean Whether $array is a filled array or an object with rows
652          */
653         public static function isResult($array)
654         {
655                 return self::getClass(Database::class)->isResult($array);
656         }
657
658         /**
659          * @brief Escapes a whole array
660          *
661          * @param mixed   $arr           Array with values to be escaped
662          * @param boolean $add_quotation add quotation marks for string values
663          * @return void
664          */
665         public static function escapeArray(&$arr, $add_quotation = false)
666         {
667                 return self::getClass(Database::class)->escapeArray($arr, $add_quotation);
668         }
669 }