]> git.mxchange.org Git - friendica.git/blob - src/Database/DBA.php
Remove deprecated code
[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          * @param string $sql SQL statement
211          * @return array first row of query
212          * @throws \Exception
213          */
214         public static function fetchFirst($sql)
215         {
216                 $params = self::getParam(func_get_args());
217
218                 return DI::dba()->fetchFirst($sql, $params);
219         }
220
221         /**
222          * Returns the number of affected rows of the last statement
223          *
224          * @return int Number of rows
225          */
226         public static function affectedRows()
227         {
228                 return DI::dba()->affectedRows();
229         }
230
231         /**
232          * Returns the number of columns of a statement
233          *
234          * @param object Statement object
235          * @return int Number of columns
236          */
237         public static function columnCount($stmt)
238         {
239                 return DI::dba()->columnCount($stmt);
240         }
241         /**
242          * Returns the number of rows of a statement
243          *
244          * @param PDOStatement|mysqli_result|mysqli_stmt Statement object
245          * @return int Number of rows
246          */
247         public static function numRows($stmt)
248         {
249                 return DI::dba()->numRows($stmt);
250         }
251
252         /**
253          * Fetch a single row
254          *
255          * @param mixed $stmt statement object
256          * @return array current row
257          */
258         public static function fetch($stmt)
259         {
260                 return DI::dba()->fetch($stmt);
261         }
262
263         /**
264          * Insert a row into a table
265          *
266          * @param string|array $table               Table name or array [schema => table]
267          * @param array        $param               parameter array
268          * @param bool         $on_duplicate_update Do an update on a duplicate entry
269          *
270          * @return boolean was the insert successful?
271          * @throws \Exception
272          */
273         public static function insert($table, $param, $on_duplicate_update = false)
274         {
275                 return DI::dba()->insert($table, $param, $on_duplicate_update);
276         }
277
278         /**
279          * Fetch the id of the last insert command
280          *
281          * @return integer Last inserted id
282          */
283         public static function lastInsertId()
284         {
285                 return DI::dba()->lastInsertId();
286         }
287
288         /**
289          * Locks a table for exclusive write access
290          *
291          * This function can be extended in the future to accept a table array as well.
292          *
293          * @param string|array $table Table name or array [schema => table]
294          *
295          * @return boolean was the lock successful?
296          * @throws \Exception
297          */
298         public static function lock($table)
299         {
300                 return DI::dba()->lock($table);
301         }
302
303         /**
304          * Unlocks all locked tables
305          *
306          * @return boolean was the unlock successful?
307          * @throws \Exception
308          */
309         public static function unlock()
310         {
311                 return DI::dba()->unlock();
312         }
313
314         /**
315          * Starts a transaction
316          *
317          * @return boolean Was the command executed successfully?
318          */
319         public static function transaction()
320         {
321                 return DI::dba()->transaction();
322         }
323
324         /**
325          * Does a commit
326          *
327          * @return boolean Was the command executed successfully?
328          */
329         public static function commit()
330         {
331                 return DI::dba()->commit();
332         }
333
334         /**
335          * Does a rollback
336          *
337          * @return boolean Was the command executed successfully?
338          */
339         public static function rollback()
340         {
341                 return DI::dba()->rollback();
342         }
343
344         /**
345          * Delete a row from a table
346          *
347          * @param string|array $table      Table name
348          * @param array        $conditions Field condition(s)
349          * @param array        $options
350          *                           - cascade: If true we delete records in other tables that depend on the one we're deleting through
351          *                           relations (default: true)
352          *
353          * @return boolean was the delete successful?
354          * @throws \Exception
355          */
356         public static function delete($table, array $conditions, array $options = [])
357         {
358                 return DI::dba()->delete($table, $conditions, $options);
359         }
360
361         /**
362          * Updates rows in the database.
363          *
364          * When $old_fields is set to an array,
365          * the system will only do an update if the fields in that array changed.
366          *
367          * Attention:
368          * Only the values in $old_fields are compared.
369          * This is an intentional behaviour.
370          *
371          * Example:
372          * We include the timestamp field in $fields but not in $old_fields.
373          * Then the row will only get the new timestamp when the other fields had changed.
374          *
375          * When $old_fields is set to a boolean value the system will do this compare itself.
376          * When $old_fields is set to "true" the system will do an insert if the row doesn't exists.
377          *
378          * Attention:
379          * Only set $old_fields to a boolean value when you are sure that you will update a single row.
380          * When you set $old_fields to "true" then $fields must contain all relevant fields!
381          *
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 }