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