]> git.mxchange.org Git - friendica.git/blob - src/Database/DBA.php
Merge pull request #7959 from MrPetovan/bug/7902-acl-fixes
[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|array $table     Table name or array [schema => table]
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|array $table               Table name or array [schema => table]
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|array $table Table name or array [schema => table]
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|array  $table      Table name or array [schema => table]
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|array $table     Table name or array [schema => 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|array $table     Table name or array [schema => table]
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|array $table     Table name or array [schema => table]
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|array $table     Table name or array [schema => table]
451          * @param array        $condition array of fields for condition
452          * @param array        $params    Array of several parameters
453          *
454          * @return int
455          *
456          * Example:
457          * $table = "item";
458          *
459          * $condition = ["uid" => 1, "network" => 'dspr'];
460          * or:
461          * $condition = ["`uid` = ? AND `network` IN (?, ?)", 1, 'dfrn', 'dspr'];
462          *
463          * $count = DBA::count($table, $condition);
464          * @throws \Exception
465          */
466         public static function count($table, array $condition = [], array $params = [])
467         {
468                 return self::getClass(Database::class)->count($table, $condition, $params);
469         }
470
471         /**
472          * Build the table query substring from one or more tables, with or without a schema.
473          *
474          * Expected formats:
475          * - table
476          * - [table1, table2, ...]
477          * - [schema1 => table1, schema2 => table2, table3, ...]
478          *
479          * @param string|array $tables
480          * @return string
481          */
482         public static function buildTableString($tables)
483         {
484                 if (is_string($tables)) {
485                         $tables = [$tables];
486                 }
487
488                 $quotedTables = [];
489
490                 foreach ($tables as $schema => $table) {
491                         if (is_numeric($schema)) {
492                                 $quotedTables[] = self::quoteIdentifier($table);
493                         } else {
494                                 $quotedTables[] = self::quoteIdentifier($schema) . '.' . self::quoteIdentifier($table);
495                         }
496                 }
497
498                 return implode(', ', $quotedTables);
499         }
500
501         /**
502          * Escape an identifier (table or field name)
503          *
504          * @param $identifier
505          * @return string
506          */
507         public static function quoteIdentifier($identifier)
508         {
509                 return '`' . str_replace('`', '``', $identifier) . '`';
510         }
511
512         /**
513          * @brief Returns the SQL condition string built from the provided condition array
514          *
515          * This function operates with two modes.
516          * - Supplied with a filed/value associative array, it builds simple strict
517          *   equality conditions linked by AND.
518          * - Supplied with a flat list, the first element is the condition string and
519          *   the following arguments are the values to be interpolated
520          *
521          * $condition = ["uid" => 1, "network" => 'dspr'];
522          * or:
523          * $condition = ["`uid` = ? AND `network` IN (?, ?)", 1, 'dfrn', 'dspr'];
524          *
525          * In either case, the provided array is left with the parameters only
526          *
527          * @param array $condition
528          * @return string
529          */
530         public static function buildCondition(array &$condition = [])
531         {
532                 $condition_string = '';
533                 if (count($condition) > 0) {
534                         reset($condition);
535                         $first_key = key($condition);
536                         if (is_int($first_key)) {
537                                 $condition_string = " WHERE (" . array_shift($condition) . ")";
538                         } else {
539                                 $new_values = [];
540                                 $condition_string = "";
541                                 foreach ($condition as $field => $value) {
542                                         if ($condition_string != "") {
543                                                 $condition_string .= " AND ";
544                                         }
545                                         if (is_array($value)) {
546                                                 /* Workaround for MySQL Bug #64791.
547                                                  * Never mix data types inside any IN() condition.
548                                                  * In case of mixed types, cast all as string.
549                                                  * Logic needs to be consistent with DBA::p() data types.
550                                                  */
551                                                 $is_int = false;
552                                                 $is_alpha = false;
553                                                 foreach ($value as $single_value) {
554                                                         if (is_int($single_value)) {
555                                                                 $is_int = true;
556                                                         } else {
557                                                                 $is_alpha = true;
558                                                         }
559                                                 }
560
561                                                 if ($is_int && $is_alpha) {
562                                                         foreach ($value as &$ref) {
563                                                                 if (is_int($ref)) {
564                                                                         $ref = (string)$ref;
565                                                                 }
566                                                         }
567                                                         unset($ref); //Prevent accidental re-use.
568                                                 }
569
570                                                 $new_values = array_merge($new_values, array_values($value));
571                                                 $placeholders = substr(str_repeat("?, ", count($value)), 0, -2);
572                                                 $condition_string .= self::quoteIdentifier($field) . " IN (" . $placeholders . ")";
573                                         } elseif (is_null($value)) {
574                                                 $condition_string .= self::quoteIdentifier($field) . " IS NULL";
575                                         } else {
576                                                 $new_values[$field] = $value;
577                                                 $condition_string .= self::quoteIdentifier($field) . " = ?";
578                                         }
579                                 }
580                                 $condition_string = " WHERE (" . $condition_string . ")";
581                                 $condition = $new_values;
582                         }
583                 }
584
585                 return $condition_string;
586         }
587
588         /**
589          * @brief Returns the SQL parameter string built from the provided parameter array
590          *
591          * @param array $params
592          * @return string
593          */
594         public static function buildParameter(array $params = [])
595         {
596                 $groupby_string = '';
597                 if (!empty($params['group_by'])) {
598                         $groupby_string = " GROUP BY " . implode(', ', array_map(['self', 'quoteIdentifier'], $params['group_by']));
599                 }
600
601                 $order_string = '';
602                 if (isset($params['order'])) {
603                         $order_string = " ORDER BY ";
604                         foreach ($params['order'] AS $fields => $order) {
605                                 if ($order === 'RAND()') {
606                                         $order_string .= "RAND(), ";
607                                 } elseif (!is_int($fields)) {
608                                         $order_string .= self::quoteIdentifier($fields) . " " . ($order ? "DESC" : "ASC") . ", ";
609                                 } else {
610                                         $order_string .= self::quoteIdentifier($order) . ", ";
611                                 }
612                         }
613                         $order_string = substr($order_string, 0, -2);
614                 }
615
616                 $limit_string = '';
617                 if (isset($params['limit']) && is_numeric($params['limit'])) {
618                         $limit_string = " LIMIT " . intval($params['limit']);
619                 }
620
621                 if (isset($params['limit']) && is_array($params['limit'])) {
622                         $limit_string = " LIMIT " . intval($params['limit'][0]) . ", " . intval($params['limit'][1]);
623                 }
624
625                 return $groupby_string . $order_string . $limit_string;
626         }
627
628         /**
629          * @brief Fills an array with data from a query
630          *
631          * @param object $stmt statement object
632          * @param bool   $do_close
633          * @return array Data array
634          */
635         public static function toArray($stmt, $do_close = true)
636         {
637                 return self::getClass(Database::class)->toArray($stmt, $do_close);
638         }
639
640         /**
641          * @brief Returns the error number of the last query
642          *
643          * @return string Error number (0 if no error)
644          */
645         public static function errorNo()
646         {
647                 return self::getClass(Database::class)->errorNo();
648         }
649
650         /**
651          * @brief Returns the error message of the last query
652          *
653          * @return string Error message ('' if no error)
654          */
655         public static function errorMessage()
656         {
657                 return self::getClass(Database::class)->errorMessage();
658         }
659
660         /**
661          * @brief Closes the current statement
662          *
663          * @param object $stmt statement object
664          * @return boolean was the close successful?
665          */
666         public static function close($stmt)
667         {
668                 return self::getClass(Database::class)->close($stmt);
669         }
670
671         /**
672          * @brief Return a list of database processes
673          *
674          * @return array
675          *      'list' => List of processes, separated in their different states
676          *      'amount' => Number of concurrent database processes
677          * @throws \Exception
678          */
679         public static function processlist()
680         {
681                 return self::getClass(Database::class)->processlist();
682         }
683
684         /**
685          * Checks if $array is a filled array with at least one entry.
686          *
687          * @param mixed $array A filled array with at least one entry
688          *
689          * @return boolean Whether $array is a filled array or an object with rows
690          */
691         public static function isResult($array)
692         {
693                 return self::getClass(Database::class)->isResult($array);
694         }
695
696         /**
697          * @brief Escapes a whole array
698          *
699          * @param mixed   $arr           Array with values to be escaped
700          * @param boolean $add_quotation add quotation marks for string values
701          * @return void
702          */
703         public static function escapeArray(&$arr, $add_quotation = false)
704         {
705                 return self::getClass(Database::class)->escapeArray($arr, $add_quotation);
706         }
707 }