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