]> git.mxchange.org Git - friendica.git/blob - src/Database/DBA.php
Merge pull request #7296 from MrPetovan/task/smilies-replace
[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
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 boolean|object
420          *
421          * Example:
422          * $table = "item";
423          * $fields = array("id", "uri", "uid", "network");
424          *
425          * $condition = array("uid" => 1, "network" => 'dspr');
426          * or:
427          * $condition = array("`uid` = ? AND `network` IN (?, ?)", 1, 'dfrn', 'dspr');
428          *
429          * $params = array("order" => array("id", "received" => true), "limit" => 10);
430          *
431          * $data = DBA::select($table, $fields, $condition, $params);
432          * @throws \Exception
433          */
434         public static function select($table, array $fields = [], array $condition = [], array $params = [])
435         {
436                 return self::$database->select($table, $fields, $condition, $params);
437         }
438
439         /**
440          * @brief Counts the rows from a table satisfying the provided condition
441          *
442          * @param string $table     Table name
443          * @param array  $condition array of fields for condition
444          *
445          * @return int
446          *
447          * Example:
448          * $table = "item";
449          *
450          * $condition = ["uid" => 1, "network" => 'dspr'];
451          * or:
452          * $condition = ["`uid` = ? AND `network` IN (?, ?)", 1, 'dfrn', 'dspr'];
453          *
454          * $count = DBA::count($table, $condition);
455          * @throws \Exception
456          */
457         public static function count($table, array $condition = [])
458         {
459                 return self::$database->count($table, $condition);
460         }
461
462         /**
463          * @brief Returns the SQL condition string built from the provided condition array
464          *
465          * This function operates with two modes.
466          * - Supplied with a filed/value associative array, it builds simple strict
467          *   equality conditions linked by AND.
468          * - Supplied with a flat list, the first element is the condition string and
469          *   the following arguments are the values to be interpolated
470          *
471          * $condition = ["uid" => 1, "network" => 'dspr'];
472          * or:
473          * $condition = ["`uid` = ? AND `network` IN (?, ?)", 1, 'dfrn', 'dspr'];
474          *
475          * In either case, the provided array is left with the parameters only
476          *
477          * @param array $condition
478          * @return string
479          */
480         public static function buildCondition(array &$condition = [])
481         {
482                 $condition_string = '';
483                 if (count($condition) > 0) {
484                         reset($condition);
485                         $first_key = key($condition);
486                         if (is_int($first_key)) {
487                                 $condition_string = " WHERE (" . array_shift($condition) . ")";
488                         } else {
489                                 $new_values = [];
490                                 $condition_string = "";
491                                 foreach ($condition as $field => $value) {
492                                         if ($condition_string != "") {
493                                                 $condition_string .= " AND ";
494                                         }
495                                         if (is_array($value)) {
496                                                 /* Workaround for MySQL Bug #64791.
497                                                  * Never mix data types inside any IN() condition.
498                                                  * In case of mixed types, cast all as string.
499                                                  * Logic needs to be consistent with DBA::p() data types.
500                                                  */
501                                                 $is_int = false;
502                                                 $is_alpha = false;
503                                                 foreach ($value as $single_value) {
504                                                         if (is_int($single_value)) {
505                                                                 $is_int = true;
506                                                         } else {
507                                                                 $is_alpha = true;
508                                                         }
509                                                 }
510
511                                                 if ($is_int && $is_alpha) {
512                                                         foreach ($value as &$ref) {
513                                                                 if (is_int($ref)) {
514                                                                         $ref = (string)$ref;
515                                                                 }
516                                                         }
517                                                         unset($ref); //Prevent accidental re-use.
518                                                 }
519
520                                                 $new_values = array_merge($new_values, array_values($value));
521                                                 $placeholders = substr(str_repeat("?, ", count($value)), 0, -2);
522                                                 $condition_string .= "`" . $field . "` IN (" . $placeholders . ")";
523                                         } elseif (is_null($value)) {
524                                                 $condition_string .= "`" . $field . "` IS NULL";
525                                         } else {
526                                                 $new_values[$field] = $value;
527                                                 $condition_string .= "`" . $field . "` = ?";
528                                         }
529                                 }
530                                 $condition_string = " WHERE (" . $condition_string . ")";
531                                 $condition = $new_values;
532                         }
533                 }
534
535                 return $condition_string;
536         }
537
538         /**
539          * @brief Returns the SQL parameter string built from the provided parameter array
540          *
541          * @param array $params
542          * @return string
543          */
544         public static function buildParameter(array $params = [])
545         {
546                 $groupby_string = '';
547                 if (isset($params['group_by'])) {
548                         $groupby_string = " GROUP BY ";
549                         foreach ($params['group_by'] as $fields) {
550                                 $groupby_string .= "`" . $fields . "`, ";
551                         }
552                         $groupby_string = substr($groupby_string, 0, -2);
553                 }
554
555                 $order_string = '';
556                 if (isset($params['order'])) {
557                         $order_string = " ORDER BY ";
558                         foreach ($params['order'] AS $fields => $order) {
559                                 if ($order === 'RAND()') {
560                                         $order_string .= "RAND(), ";
561                                 } elseif (!is_int($fields)) {
562                                         $order_string .= "`" . $fields . "` " . ($order ? "DESC" : "ASC") . ", ";
563                                 } else {
564                                         $order_string .= "`" . $order . "`, ";
565                                 }
566                         }
567                         $order_string = substr($order_string, 0, -2);
568                 }
569
570                 $limit_string = '';
571                 if (isset($params['limit']) && is_numeric($params['limit'])) {
572                         $limit_string = " LIMIT " . intval($params['limit']);
573                 }
574
575                 if (isset($params['limit']) && is_array($params['limit'])) {
576                         $limit_string = " LIMIT " . intval($params['limit'][0]) . ", " . intval($params['limit'][1]);
577                 }
578
579                 return $groupby_string . $order_string . $limit_string;
580         }
581
582         /**
583          * @brief Fills an array with data from a query
584          *
585          * @param object $stmt statement object
586          * @param bool   $do_close
587          * @return array Data array
588          */
589         public static function toArray($stmt, $do_close = true)
590         {
591                 return self::$database->toArray($stmt, $do_close);
592         }
593
594         /**
595          * @brief Returns the error number of the last query
596          *
597          * @return string Error number (0 if no error)
598          */
599         public static function errorNo()
600         {
601                 return self::$database->errorNo();
602         }
603
604         /**
605          * @brief Returns the error message of the last query
606          *
607          * @return string Error message ('' if no error)
608          */
609         public static function errorMessage()
610         {
611                 return self::$database->errorMessage();
612         }
613
614         /**
615          * @brief Closes the current statement
616          *
617          * @param object $stmt statement object
618          * @return boolean was the close successful?
619          */
620         public static function close($stmt)
621         {
622                 return self::$database->close($stmt);
623         }
624
625         /**
626          * @brief Return a list of database processes
627          *
628          * @return array
629          *      'list' => List of processes, separated in their different states
630          *      'amount' => Number of concurrent database processes
631          * @throws \Exception
632          */
633         public static function processlist()
634         {
635                 return self::$database->processlist();
636         }
637
638         /**
639          * Checks if $array is a filled array with at least one entry.
640          *
641          * @param mixed $array A filled array with at least one entry
642          *
643          * @return boolean Whether $array is a filled array or an object with rows
644          */
645         public static function isResult($array)
646         {
647                 return self::$database->isResult($array);
648         }
649
650         /**
651          * @brief Escapes a whole array
652          *
653          * @param mixed   $arr           Array with values to be escaped
654          * @param boolean $add_quotation add quotation marks for string values
655          * @return void
656          */
657         public static function escapeArray(&$arr, $add_quotation = false)
658         {
659                 return self::$database->escapeArray($arr, $add_quotation);
660         }
661 }