[!!!][TASK] Make TimeTracker a singleton
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Database / DatabaseConnection.php
1 <?php
2 namespace TYPO3\CMS\Core\Database;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use TYPO3\CMS\Core\TimeTracker\TimeTracker;
18 use TYPO3\CMS\Core\Utility\GeneralUtility;
19 use TYPO3\CMS\Core\Utility\StringUtility;
20
21 /**
22 * Contains the class "DatabaseConnection" containing functions for building SQL queries
23 * and mysqli wrappers, thus providing a foundational API to all database
24 * interaction.
25 * This class is instantiated globally as $TYPO3_DB in TYPO3 scripts.
26 *
27 * TYPO3 "database wrapper" class (new in 3.6.0)
28 * This class contains
29 * - abstraction functions for executing INSERT/UPDATE/DELETE/SELECT queries ("Query execution"; These are REQUIRED for all future connectivity to the database, thus ensuring DBAL compliance!)
30 * - functions for building SQL queries (INSERT/UPDATE/DELETE/SELECT) ("Query building"); These are transitional functions for building SQL queries in a more automated way. Use these to build queries instead of doing it manually in your code!
31 * - mysqli wrapper functions; These are transitional functions. By a simple search/replace you should be able to substitute all mysql*() calls with $GLOBALS['TYPO3_DB']->sql*() and your application will work out of the box. YOU CANNOT (legally) use any mysqli functions not found as wrapper functions in this class!
32 * See the Project Coding Guidelines (doc_core_cgl) for more instructions on best-practise
33 *
34 * This class is not in itself a complete database abstraction layer but can be extended to be a DBAL (by extensions, see "dbal" for example)
35 * ALL connectivity to the database in TYPO3 must be done through this class!
36 * The points of this class are:
37 * - To direct all database calls through this class so it becomes possible to implement DBAL with extensions.
38 * - To keep it very easy to use for developers used to MySQL in PHP - and preserve as much performance as possible when TYPO3 is used with MySQL directly...
39 * - To create an interface for DBAL implemented by extensions; (Eg. making possible escaping characters, clob/blob handling, reserved words handling)
40 * - Benchmarking the DB bottleneck queries will become much easier; Will make it easier to find optimization possibilities.
41 *
42 * USE:
43 * In all TYPO3 scripts the global variable $TYPO3_DB is an instance of this class. Use that.
44 * Eg. $GLOBALS['TYPO3_DB']->sql_fetch_assoc()
45 */
46 class DatabaseConnection
47 {
48 /**
49 * The AND constraint in where clause
50 *
51 * @var string
52 */
53 const AND_Constraint = 'AND';
54
55 /**
56 * The OR constraint in where clause
57 *
58 * @var string
59 */
60 const OR_Constraint = 'OR';
61
62 /**
63 * Set "TRUE" or "1" if you want database errors outputted. Set to "2" if you also want successful database actions outputted.
64 *
65 * @var bool|int
66 */
67 public $debugOutput = false;
68
69 /**
70 * Internally: Set to last built query (not necessarily executed...)
71 *
72 * @var string
73 */
74 public $debug_lastBuiltQuery = '';
75
76 /**
77 * Set "TRUE" if you want the last built query to be stored in $debug_lastBuiltQuery independent of $this->debugOutput
78 *
79 * @var bool
80 */
81 public $store_lastBuiltQuery = false;
82
83 /**
84 * Set this to 1 to get queries explained (devIPmask must match). Set the value to 2 to the same but disregarding the devIPmask.
85 * There is an alternative option to enable explain output in the admin panel under "TypoScript", which will produce much nicer output, but only works in FE.
86 *
87 * @var bool
88 */
89 public $explainOutput = 0;
90
91 /**
92 * @var string Database host to connect to
93 */
94 protected $databaseHost = '';
95
96 /**
97 * @var int Database port to connect to
98 */
99 protected $databasePort = 3306;
100
101 /**
102 * @var string|NULL Database socket to connect to
103 */
104 protected $databaseSocket = null;
105
106 /**
107 * @var string Database name to connect to
108 */
109 protected $databaseName = '';
110
111 /**
112 * @var string Database user to connect with
113 */
114 protected $databaseUsername = '';
115
116 /**
117 * @var string Database password to connect with
118 */
119 protected $databaseUserPassword = '';
120
121 /**
122 * @var bool TRUE if database connection should be persistent
123 * @see http://php.net/manual/de/mysqli.persistconns.php
124 */
125 protected $persistentDatabaseConnection = false;
126
127 /**
128 * @var bool TRUE if connection between client and sql server is compressed
129 */
130 protected $connectionCompression = false;
131
132 /**
133 * The charset for the connection; will be passed on to
134 * mysqli_set_charset during connection initialization.
135 *
136 * @var string
137 */
138 protected $connectionCharset = 'utf8';
139
140 /**
141 * @var array List of commands executed after connection was established
142 */
143 protected $initializeCommandsAfterConnect = array();
144
145 /**
146 * @var bool TRUE if database connection is established
147 */
148 protected $isConnected = false;
149
150 /**
151 * @var \mysqli $link Default database link object
152 */
153 protected $link = null;
154
155 /**
156 * Default character set, applies unless character set or collation are explicitly set
157 *
158 * @var string
159 */
160 public $default_charset = 'utf8';
161
162 /**
163 * @var array<PostProcessQueryHookInterface>
164 */
165 protected $preProcessHookObjects = array();
166
167 /**
168 * @var array<PreProcessQueryHookInterface>
169 */
170 protected $postProcessHookObjects = array();
171
172 /**
173 * the date and time formats compatible with the database in general
174 *
175 * @var array
176 */
177 protected static $dateTimeFormats = array(
178 'date' => array(
179 'empty' => '0000-00-00',
180 'format' => 'Y-m-d'
181 ),
182 'datetime' => array(
183 'empty' => '0000-00-00 00:00:00',
184 'format' => 'Y-m-d H:i:s'
185 )
186 );
187
188 /**
189 * Initialize the database connection
190 *
191 * @return void
192 */
193 public function initialize()
194 {
195 // Intentionally blank as this will be overloaded by DBAL
196 }
197
198 /************************************
199 *
200 * Query execution
201 *
202 * These functions are the RECOMMENDED DBAL functions for use in your applications
203 * Using these functions will allow the DBAL to use alternative ways of accessing data (contrary to if a query is returned!)
204 * They compile a query AND execute it immediately and then return the result
205 * This principle heightens our ability to create various forms of DBAL of the functions.
206 * Generally: We want to return a result pointer/object, never queries.
207 * Also, having the table name together with the actual query execution allows us to direct the request to other databases.
208 *
209 **************************************/
210
211 /**
212 * Creates and executes an INSERT SQL-statement for $table from the array with field/value pairs $fields_values.
213 * Using this function specifically allows us to handle BLOB and CLOB fields depending on DB
214 *
215 * @param string $table Table name
216 * @param array $fields_values Field values as key=>value pairs. Values will be escaped internally. Typically you would fill an array like "$insertFields" with 'fieldname'=>'value' and pass it to this function as argument.
217 * @param bool|array|string $no_quote_fields See fullQuoteArray()
218 * @return bool|\mysqli_result|object MySQLi result object / DBAL object
219 */
220 public function exec_INSERTquery($table, $fields_values, $no_quote_fields = false)
221 {
222 $res = $this->query($this->INSERTquery($table, $fields_values, $no_quote_fields));
223 if ($this->debugOutput) {
224 $this->debug('exec_INSERTquery');
225 }
226 foreach ($this->postProcessHookObjects as $hookObject) {
227 /** @var $hookObject PostProcessQueryHookInterface */
228 $hookObject->exec_INSERTquery_postProcessAction($table, $fields_values, $no_quote_fields, $this);
229 }
230 return $res;
231 }
232
233 /**
234 * Creates and executes an INSERT SQL-statement for $table with multiple rows.
235 *
236 * @param string $table Table name
237 * @param array $fields Field names
238 * @param array $rows Table rows. Each row should be an array with field values mapping to $fields
239 * @param bool|array|string $no_quote_fields See fullQuoteArray()
240 * @return bool|\mysqli_result|object MySQLi result object / DBAL object
241 */
242 public function exec_INSERTmultipleRows($table, array $fields, array $rows, $no_quote_fields = false)
243 {
244 $res = $this->query($this->INSERTmultipleRows($table, $fields, $rows, $no_quote_fields));
245 if ($this->debugOutput) {
246 $this->debug('exec_INSERTmultipleRows');
247 }
248 foreach ($this->postProcessHookObjects as $hookObject) {
249 /** @var $hookObject PostProcessQueryHookInterface */
250 $hookObject->exec_INSERTmultipleRows_postProcessAction($table, $fields, $rows, $no_quote_fields, $this);
251 }
252 return $res;
253 }
254
255 /**
256 * Creates and executes an UPDATE SQL-statement for $table where $where-clause (typ. 'uid=...') from the array with field/value pairs $fields_values.
257 * Using this function specifically allow us to handle BLOB and CLOB fields depending on DB
258 *
259 * @param string $table Database tablename
260 * @param string $where WHERE clause, eg. "uid=1". NOTICE: You must escape values in this argument with $this->fullQuoteStr() yourself!
261 * @param array $fields_values Field values as key=>value pairs. Values will be escaped internally. Typically you would fill an array like "$updateFields" with 'fieldname'=>'value' and pass it to this function as argument.
262 * @param bool|array|string $no_quote_fields See fullQuoteArray()
263 * @return bool|\mysqli_result|object MySQLi result object / DBAL object
264 */
265 public function exec_UPDATEquery($table, $where, $fields_values, $no_quote_fields = false)
266 {
267 $res = $this->query($this->UPDATEquery($table, $where, $fields_values, $no_quote_fields));
268 if ($this->debugOutput) {
269 $this->debug('exec_UPDATEquery');
270 }
271 foreach ($this->postProcessHookObjects as $hookObject) {
272 /** @var $hookObject PostProcessQueryHookInterface */
273 $hookObject->exec_UPDATEquery_postProcessAction($table, $where, $fields_values, $no_quote_fields, $this);
274 }
275 return $res;
276 }
277
278 /**
279 * Creates and executes a DELETE SQL-statement for $table where $where-clause
280 *
281 * @param string $table Database tablename
282 * @param string $where WHERE clause, eg. "uid=1". NOTICE: You must escape values in this argument with $this->fullQuoteStr() yourself!
283 * @return bool|\mysqli_result|object MySQLi result object / DBAL object
284 */
285 public function exec_DELETEquery($table, $where)
286 {
287 $res = $this->query($this->DELETEquery($table, $where));
288 if ($this->debugOutput) {
289 $this->debug('exec_DELETEquery');
290 }
291 foreach ($this->postProcessHookObjects as $hookObject) {
292 /** @var $hookObject PostProcessQueryHookInterface */
293 $hookObject->exec_DELETEquery_postProcessAction($table, $where, $this);
294 }
295 return $res;
296 }
297
298 /**
299 * Creates and executes a SELECT SQL-statement
300 * Using this function specifically allow us to handle the LIMIT feature independently of DB.
301 *
302 * @param string $select_fields List of fields to select from the table. This is what comes right after "SELECT ...". Required value.
303 * @param string $from_table Table(s) from which to select. This is what comes right after "FROM ...". Required value.
304 * @param string $where_clause Additional WHERE clauses put in the end of the query. NOTICE: You must escape values in this argument with $this->fullQuoteStr() yourself! DO NOT PUT IN GROUP BY, ORDER BY or LIMIT!
305 * @param string $groupBy Optional GROUP BY field(s), if none, supply blank string.
306 * @param string $orderBy Optional ORDER BY field(s), if none, supply blank string.
307 * @param string $limit Optional LIMIT value ([begin,]max), if none, supply blank string.
308 * @return bool|\mysqli_result|object MySQLi result object / DBAL object
309 */
310 public function exec_SELECTquery($select_fields, $from_table, $where_clause, $groupBy = '', $orderBy = '', $limit = '')
311 {
312 $query = $this->SELECTquery($select_fields, $from_table, $where_clause, $groupBy, $orderBy, $limit);
313 $res = $this->query($query);
314 if ($this->debugOutput) {
315 $this->debug('exec_SELECTquery');
316 }
317 if ($this->explainOutput) {
318 $this->explain($query, $from_table, $res->num_rows);
319 }
320 foreach ($this->postProcessHookObjects as $hookObject) {
321 /** @var $hookObject PostProcessQueryHookInterface */
322 $hookObject->exec_SELECTquery_postProcessAction($select_fields, $from_table, $where_clause, $groupBy = '', $orderBy = '', $limit = '', $this);
323 }
324 return $res;
325 }
326
327 /**
328 * Creates and executes a SELECT query, selecting fields ($select) from two/three tables joined
329 * Use $mm_table together with $local_table or $foreign_table to select over two tables. Or use all three tables to select the full MM-relation.
330 * The JOIN is done with [$local_table].uid <--> [$mm_table].uid_local / [$mm_table].uid_foreign <--> [$foreign_table].uid
331 * The function is very useful for selecting MM-relations between tables adhering to the MM-format used by TCE (TYPO3 Core Engine). See the section on $GLOBALS['TCA'] in Inside TYPO3 for more details.
332 *
333 * @param string $select Field list for SELECT
334 * @param string $local_table Tablename, local table
335 * @param string $mm_table Tablename, relation table
336 * @param string $foreign_table Tablename, foreign table
337 * @param string $whereClause Optional additional WHERE clauses put in the end of the query. NOTICE: You must escape values in this argument with $this->fullQuoteStr() yourself! DO NOT PUT IN GROUP BY, ORDER BY or LIMIT! You have to prepend 'AND ' to this parameter yourself!
338 * @param string $groupBy Optional GROUP BY field(s), if none, supply blank string.
339 * @param string $orderBy Optional ORDER BY field(s), if none, supply blank string.
340 * @param string $limit Optional LIMIT value ([begin,]max), if none, supply blank string.
341 * @return bool|\mysqli_result|object MySQLi result object / DBAL object
342 * @see exec_SELECTquery()
343 */
344 public function exec_SELECT_mm_query($select, $local_table, $mm_table, $foreign_table, $whereClause = '', $groupBy = '', $orderBy = '', $limit = '')
345 {
346 $queryParts = $this->getSelectMmQueryParts($select, $local_table, $mm_table, $foreign_table, $whereClause, $groupBy, $orderBy, $limit);
347 return $this->exec_SELECT_queryArray($queryParts);
348 }
349
350 /**
351 * Executes a select based on input query parts array
352 *
353 * @param array $queryParts Query parts array
354 * @return bool|\mysqli_result|object MySQLi result object / DBAL object
355 * @see exec_SELECTquery()
356 */
357 public function exec_SELECT_queryArray($queryParts)
358 {
359 return $this->exec_SELECTquery($queryParts['SELECT'], $queryParts['FROM'], $queryParts['WHERE'], $queryParts['GROUPBY'], $queryParts['ORDERBY'], $queryParts['LIMIT']);
360 }
361
362 /**
363 * Creates and executes a SELECT SQL-statement AND traverse result set and returns array with records in.
364 *
365 * @param string $select_fields List of fields to select from the table. This is what comes right after "SELECT ...". Required value.
366 * @param string $from_table Table(s) from which to select. This is what comes right after "FROM ...". Required value.
367 * @param string $where_clause Additional WHERE clauses put in the end of the query. NOTICE: You must escape values in this argument with $this->fullQuoteStr() yourself! DO NOT PUT IN GROUP BY, ORDER BY or LIMIT!
368 * @param string $groupBy Optional GROUP BY field(s), if none, supply blank string.
369 * @param string $orderBy Optional ORDER BY field(s), if none, supply blank string.
370 * @param string $limit Optional LIMIT value ([begin,]max), if none, supply blank string.
371 * @param string $uidIndexField If set, the result array will carry this field names value as index. Requires that field to be selected of course!
372 * @return array|NULL Array of rows, or NULL in case of SQL error
373 * @see exec_SELECTquery()
374 * @throws \InvalidArgumentException
375 */
376 public function exec_SELECTgetRows($select_fields, $from_table, $where_clause, $groupBy = '', $orderBy = '', $limit = '', $uidIndexField = '')
377 {
378 $res = $this->exec_SELECTquery($select_fields, $from_table, $where_clause, $groupBy, $orderBy, $limit);
379 if ($this->sql_error()) {
380 $this->sql_free_result($res);
381 return null;
382 }
383 $output = array();
384 $firstRecord = true;
385 while ($record = $this->sql_fetch_assoc($res)) {
386 if ($uidIndexField) {
387 if ($firstRecord) {
388 $firstRecord = false;
389 if (!array_key_exists($uidIndexField, $record)) {
390 $this->sql_free_result($res);
391 throw new \InvalidArgumentException('The given $uidIndexField "' . $uidIndexField . '" is not available in the result.', 1432933855);
392 }
393 }
394 $output[$record[$uidIndexField]] = $record;
395 } else {
396 $output[] = $record;
397 }
398 }
399 $this->sql_free_result($res);
400 return $output;
401 }
402
403 /**
404 * Creates and executes a SELECT SQL-statement AND gets a result set and returns an array with a single record in.
405 * LIMIT is automatically set to 1 and can not be overridden.
406 *
407 * @param string $select_fields List of fields to select from the table.
408 * @param string $from_table Table(s) from which to select.
409 * @param string $where_clause Optional additional WHERE clauses put in the end of the query. NOTICE: You must escape values in this argument with $this->fullQuoteStr() yourself!
410 * @param string $groupBy Optional GROUP BY field(s), if none, supply blank string.
411 * @param string $orderBy Optional ORDER BY field(s), if none, supply blank string.
412 * @param bool $numIndex If set, the result will be fetched with sql_fetch_row, otherwise sql_fetch_assoc will be used.
413 * @return array|FALSE|NULL Single row, FALSE on empty result, NULL on error
414 */
415 public function exec_SELECTgetSingleRow($select_fields, $from_table, $where_clause, $groupBy = '', $orderBy = '', $numIndex = false)
416 {
417 $res = $this->exec_SELECTquery($select_fields, $from_table, $where_clause, $groupBy, $orderBy, '1');
418 $output = null;
419 if ($res !== false) {
420 if ($numIndex) {
421 $output = $this->sql_fetch_row($res);
422 } else {
423 $output = $this->sql_fetch_assoc($res);
424 }
425 $this->sql_free_result($res);
426 }
427 return $output;
428 }
429
430 /**
431 * Counts the number of rows in a table.
432 *
433 * @param string $field Name of the field to use in the COUNT() expression (e.g. '*')
434 * @param string $table Name of the table to count rows for
435 * @param string $where (optional) WHERE statement of the query
436 * @return mixed Number of rows counter (int) or FALSE if something went wrong (bool)
437 */
438 public function exec_SELECTcountRows($field, $table, $where = '1=1')
439 {
440 $count = false;
441 $resultSet = $this->exec_SELECTquery('COUNT(' . $field . ')', $table, $where);
442 if ($resultSet !== false) {
443 list($count) = $this->sql_fetch_row($resultSet);
444 $count = (int)$count;
445 $this->sql_free_result($resultSet);
446 }
447 return $count;
448 }
449
450 /**
451 * Truncates a table.
452 *
453 * @param string $table Database tablename
454 * @return mixed Result from handler
455 */
456 public function exec_TRUNCATEquery($table)
457 {
458 $res = $this->query($this->TRUNCATEquery($table));
459 if ($this->debugOutput) {
460 $this->debug('exec_TRUNCATEquery');
461 }
462 foreach ($this->postProcessHookObjects as $hookObject) {
463 /** @var $hookObject PostProcessQueryHookInterface */
464 $hookObject->exec_TRUNCATEquery_postProcessAction($table, $this);
465 }
466 return $res;
467 }
468
469 /**
470 * Central query method. Also checks if there is a database connection.
471 * Use this to execute database queries instead of directly calling $this->link->query()
472 *
473 * @param string $query The query to send to the database
474 * @return bool|\mysqli_result
475 */
476 protected function query($query)
477 {
478 if (!$this->isConnected) {
479 $this->connectDB();
480 }
481 return $this->link->query($query);
482 }
483
484 /**************************************
485 *
486 * Query building
487 *
488 **************************************/
489 /**
490 * Creates an INSERT SQL-statement for $table from the array with field/value pairs $fields_values.
491 *
492 * @param string $table See exec_INSERTquery()
493 * @param array $fields_values See exec_INSERTquery()
494 * @param bool|array|string $no_quote_fields See fullQuoteArray()
495 * @return string|NULL Full SQL query for INSERT, NULL if $fields_values is empty
496 */
497 public function INSERTquery($table, $fields_values, $no_quote_fields = false)
498 {
499 // Table and fieldnames should be "SQL-injection-safe" when supplied to this
500 // function (contrary to values in the arrays which may be insecure).
501 if (!is_array($fields_values) || empty($fields_values)) {
502 return null;
503 }
504 foreach ($this->preProcessHookObjects as $hookObject) {
505 $hookObject->INSERTquery_preProcessAction($table, $fields_values, $no_quote_fields, $this);
506 }
507 // Quote and escape values
508 $fields_values = $this->fullQuoteArray($fields_values, $table, $no_quote_fields, true);
509 // Build query
510 $query = 'INSERT INTO ' . $table . ' (' . implode(',', array_keys($fields_values)) . ') VALUES ' . '(' . implode(',', $fields_values) . ')';
511 // Return query
512 if ($this->debugOutput || $this->store_lastBuiltQuery) {
513 $this->debug_lastBuiltQuery = $query;
514 }
515 return $query;
516 }
517
518 /**
519 * Creates an INSERT SQL-statement for $table with multiple rows.
520 *
521 * @param string $table Table name
522 * @param array $fields Field names
523 * @param array $rows Table rows. Each row should be an array with field values mapping to $fields
524 * @param bool|array|string $no_quote_fields See fullQuoteArray()
525 * @return string|NULL Full SQL query for INSERT, NULL if $rows is empty
526 */
527 public function INSERTmultipleRows($table, array $fields, array $rows, $no_quote_fields = false)
528 {
529 // Table and fieldnames should be "SQL-injection-safe" when supplied to this
530 // function (contrary to values in the arrays which may be insecure).
531 if (empty($rows)) {
532 return null;
533 }
534 foreach ($this->preProcessHookObjects as $hookObject) {
535 /** @var $hookObject PreProcessQueryHookInterface */
536 $hookObject->INSERTmultipleRows_preProcessAction($table, $fields, $rows, $no_quote_fields, $this);
537 }
538 // Build query
539 $query = 'INSERT INTO ' . $table . ' (' . implode(', ', $fields) . ') VALUES ';
540 $rowSQL = array();
541 foreach ($rows as $row) {
542 // Quote and escape values
543 $row = $this->fullQuoteArray($row, $table, $no_quote_fields);
544 $rowSQL[] = '(' . implode(', ', $row) . ')';
545 }
546 $query .= implode(', ', $rowSQL);
547 // Return query
548 if ($this->debugOutput || $this->store_lastBuiltQuery) {
549 $this->debug_lastBuiltQuery = $query;
550 }
551 return $query;
552 }
553
554 /**
555 * Creates an UPDATE SQL-statement for $table where $where-clause (typ. 'uid=...') from the array with field/value pairs $fields_values.
556 *
557 *
558 * @param string $table See exec_UPDATEquery()
559 * @param string $where See exec_UPDATEquery()
560 * @param array $fields_values See exec_UPDATEquery()
561 * @param bool|array|string $no_quote_fields See fullQuoteArray()
562 * @throws \InvalidArgumentException
563 * @return string Full SQL query for UPDATE
564 */
565 public function UPDATEquery($table, $where, $fields_values, $no_quote_fields = false)
566 {
567 // Table and fieldnames should be "SQL-injection-safe" when supplied to this
568 // function (contrary to values in the arrays which may be insecure).
569 if (is_string($where)) {
570 foreach ($this->preProcessHookObjects as $hookObject) {
571 /** @var $hookObject PreProcessQueryHookInterface */
572 $hookObject->UPDATEquery_preProcessAction($table, $where, $fields_values, $no_quote_fields, $this);
573 }
574 $fields = array();
575 if (is_array($fields_values) && !empty($fields_values)) {
576 // Quote and escape values
577 $nArr = $this->fullQuoteArray($fields_values, $table, $no_quote_fields, true);
578 foreach ($nArr as $k => $v) {
579 $fields[] = $k . '=' . $v;
580 }
581 }
582 // Build query
583 $query = 'UPDATE ' . $table . ' SET ' . implode(',', $fields) . ((string)$where !== '' ? ' WHERE ' . $where : '');
584 if ($this->debugOutput || $this->store_lastBuiltQuery) {
585 $this->debug_lastBuiltQuery = $query;
586 }
587 return $query;
588 } else {
589 throw new \InvalidArgumentException('TYPO3 Fatal Error: "Where" clause argument for UPDATE query was not a string in $this->UPDATEquery() !', 1270853880);
590 }
591 }
592
593 /**
594 * Creates a DELETE SQL-statement for $table where $where-clause
595 *
596 * @param string $table See exec_DELETEquery()
597 * @param string $where See exec_DELETEquery()
598 * @return string Full SQL query for DELETE
599 * @throws \InvalidArgumentException
600 */
601 public function DELETEquery($table, $where)
602 {
603 if (is_string($where)) {
604 foreach ($this->preProcessHookObjects as $hookObject) {
605 /** @var $hookObject PreProcessQueryHookInterface */
606 $hookObject->DELETEquery_preProcessAction($table, $where, $this);
607 }
608 // Table and fieldnames should be "SQL-injection-safe" when supplied to this function
609 $query = 'DELETE FROM ' . $table . ((string)$where !== '' ? ' WHERE ' . $where : '');
610 if ($this->debugOutput || $this->store_lastBuiltQuery) {
611 $this->debug_lastBuiltQuery = $query;
612 }
613 return $query;
614 } else {
615 throw new \InvalidArgumentException('TYPO3 Fatal Error: "Where" clause argument for DELETE query was not a string in $this->DELETEquery() !', 1270853881);
616 }
617 }
618
619 /**
620 * Creates a SELECT SQL-statement
621 *
622 * @param string $select_fields See exec_SELECTquery()
623 * @param string $from_table See exec_SELECTquery()
624 * @param string $where_clause See exec_SELECTquery()
625 * @param string $groupBy See exec_SELECTquery()
626 * @param string $orderBy See exec_SELECTquery()
627 * @param string $limit See exec_SELECTquery()
628 * @return string Full SQL query for SELECT
629 */
630 public function SELECTquery($select_fields, $from_table, $where_clause, $groupBy = '', $orderBy = '', $limit = '')
631 {
632 foreach ($this->preProcessHookObjects as $hookObject) {
633 /** @var $hookObject PreProcessQueryHookInterface */
634 $hookObject->SELECTquery_preProcessAction($select_fields, $from_table, $where_clause, $groupBy, $orderBy, $limit, $this);
635 }
636 // Table and fieldnames should be "SQL-injection-safe" when supplied to this function
637 // Build basic query
638 $query = 'SELECT ' . $select_fields . ' FROM ' . $from_table . ((string)$where_clause !== '' ? ' WHERE ' . $where_clause : '');
639 // Group by
640 $query .= (string)$groupBy !== '' ? ' GROUP BY ' . $groupBy : '';
641 // Order by
642 $query .= (string)$orderBy !== '' ? ' ORDER BY ' . $orderBy : '';
643 // Group by
644 $query .= (string)$limit !== '' ? ' LIMIT ' . $limit : '';
645 // Return query
646 if ($this->debugOutput || $this->store_lastBuiltQuery) {
647 $this->debug_lastBuiltQuery = $query;
648 }
649 return $query;
650 }
651
652 /**
653 * Creates a SELECT SQL-statement to be used as subquery within another query.
654 * BEWARE: This method should not be overriden within DBAL to prevent quoting from happening.
655 *
656 * @param string $select_fields List of fields to select from the table.
657 * @param string $from_table Table from which to select.
658 * @param string $where_clause Conditional WHERE statement
659 * @return string Full SQL query for SELECT
660 */
661 public function SELECTsubquery($select_fields, $from_table, $where_clause)
662 {
663 // Table and fieldnames should be "SQL-injection-safe" when supplied to this function
664 // Build basic query:
665 $query = 'SELECT ' . $select_fields . ' FROM ' . $from_table . ((string)$where_clause !== '' ? ' WHERE ' . $where_clause : '');
666 // Return query
667 if ($this->debugOutput || $this->store_lastBuiltQuery) {
668 $this->debug_lastBuiltQuery = $query;
669 }
670 return $query;
671 }
672
673 /**
674 * Creates a SELECT query, selecting fields ($select) from two/three tables joined
675 * Use $mm_table together with $local_table or $foreign_table to select over two tables. Or use all three tables to select the full MM-relation.
676 * The JOIN is done with [$local_table].uid <--> [$mm_table].uid_local / [$mm_table].uid_foreign <--> [$foreign_table].uid
677 * The function is very useful for selecting MM-relations between tables adhering to the MM-format used by TCE (TYPO3 Core Engine). See the section on $GLOBALS['TCA'] in Inside TYPO3 for more details.
678 *
679 * @param string $select See exec_SELECT_mm_query()
680 * @param string $local_table See exec_SELECT_mm_query()
681 * @param string $mm_table See exec_SELECT_mm_query()
682 * @param string $foreign_table See exec_SELECT_mm_query()
683 * @param string $whereClause See exec_SELECT_mm_query()
684 * @param string $groupBy See exec_SELECT_mm_query()
685 * @param string $orderBy See exec_SELECT_mm_query()
686 * @param string $limit See exec_SELECT_mm_query()
687 * @return string Full SQL query for SELECT
688 * @see SELECTquery()
689 */
690 public function SELECT_mm_query($select, $local_table, $mm_table, $foreign_table, $whereClause = '', $groupBy = '', $orderBy = '', $limit = '')
691 {
692 $queryParts = $this->getSelectMmQueryParts($select, $local_table, $mm_table, $foreign_table, $whereClause, $groupBy, $orderBy, $limit);
693 return $this->SELECTquery($queryParts['SELECT'], $queryParts['FROM'], $queryParts['WHERE'], $queryParts['GROUPBY'], $queryParts['ORDERBY'], $queryParts['LIMIT']);
694 }
695
696 /**
697 * Creates a TRUNCATE TABLE SQL-statement
698 *
699 * @param string $table See exec_TRUNCATEquery()
700 * @return string Full SQL query for TRUNCATE TABLE
701 */
702 public function TRUNCATEquery($table)
703 {
704 foreach ($this->preProcessHookObjects as $hookObject) {
705 /** @var $hookObject PreProcessQueryHookInterface */
706 $hookObject->TRUNCATEquery_preProcessAction($table, $this);
707 }
708 // Table should be "SQL-injection-safe" when supplied to this function
709 // Build basic query:
710 $query = 'TRUNCATE TABLE ' . $table;
711 // Return query:
712 if ($this->debugOutput || $this->store_lastBuiltQuery) {
713 $this->debug_lastBuiltQuery = $query;
714 }
715 return $query;
716 }
717
718 /**
719 * Returns a WHERE clause that can find a value ($value) in a list field ($field)
720 * For instance a record in the database might contain a list of numbers,
721 * "34,234,5" (with no spaces between). This query would be able to select that
722 * record based on the value "34", "234" or "5" regardless of their position in
723 * the list (left, middle or right).
724 * The value must not contain a comma (,)
725 * Is nice to look up list-relations to records or files in TYPO3 database tables.
726 *
727 * @param string $field Field name
728 * @param string $value Value to find in list
729 * @param string $table Table in which we are searching (for DBAL detection of quoteStr() method)
730 * @return string WHERE clause for a query
731 * @throws \InvalidArgumentException
732 */
733 public function listQuery($field, $value, $table)
734 {
735 $value = (string)$value;
736 if (strpos($value, ',') !== false) {
737 throw new \InvalidArgumentException('$value must not contain a comma (,) in $this->listQuery() !', 1294585862);
738 }
739 $pattern = $this->quoteStr($value, $table);
740 $where = 'FIND_IN_SET(\'' . $pattern . '\',' . $field . ')';
741 return $where;
742 }
743
744 /**
745 * Returns a WHERE clause which will make an AND or OR search for the words in the $searchWords array in any of the fields in array $fields.
746 *
747 * @param array $searchWords Array of search words
748 * @param array $fields Array of fields
749 * @param string $table Table in which we are searching (for DBAL detection of quoteStr() method)
750 * @param string $constraint How multiple search words have to match ('AND' or 'OR')
751 * @return string WHERE clause for search
752 */
753 public function searchQuery($searchWords, $fields, $table, $constraint = self::AND_Constraint)
754 {
755 switch ($constraint) {
756 case self::OR_Constraint:
757 $constraint = 'OR';
758 break;
759 default:
760 $constraint = 'AND';
761 }
762
763 $queryParts = array();
764 foreach ($searchWords as $sw) {
765 $like = ' LIKE \'%' . $this->quoteStr($this->escapeStrForLike($sw, $table), $table) . '%\'';
766 $queryParts[] = $table . '.' . implode(($like . ' OR ' . $table . '.'), $fields) . $like;
767 }
768 $query = '(' . implode(') ' . $constraint . ' (', $queryParts) . ')';
769
770 return $query;
771 }
772
773 /**************************************
774 *
775 * Prepared Query Support
776 *
777 **************************************/
778 /**
779 * Creates a SELECT prepared SQL statement.
780 *
781 * @param string $select_fields See exec_SELECTquery()
782 * @param string $from_table See exec_SELECTquery()
783 * @param string $where_clause See exec_SELECTquery()
784 * @param string $groupBy See exec_SELECTquery()
785 * @param string $orderBy See exec_SELECTquery()
786 * @param string $limit See exec_SELECTquery()
787 * @param array $input_parameters An array of values with as many elements as there are bound parameters in the SQL statement being executed. All values are treated as \TYPO3\CMS\Core\Database\PreparedStatement::PARAM_AUTOTYPE.
788 * @return \TYPO3\CMS\Core\Database\PreparedStatement Prepared statement
789 */
790 public function prepare_SELECTquery($select_fields, $from_table, $where_clause, $groupBy = '', $orderBy = '', $limit = '', array $input_parameters = array())
791 {
792 $query = $this->SELECTquery($select_fields, $from_table, $where_clause, $groupBy, $orderBy, $limit);
793 /** @var $preparedStatement \TYPO3\CMS\Core\Database\PreparedStatement */
794 $preparedStatement = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\PreparedStatement::class, $query, $from_table, array());
795 // Bind values to parameters
796 foreach ($input_parameters as $key => $value) {
797 $preparedStatement->bindValue($key, $value, PreparedStatement::PARAM_AUTOTYPE);
798 }
799 // Return prepared statement
800 return $preparedStatement;
801 }
802
803 /**
804 * Creates a SELECT prepared SQL statement based on input query parts array
805 *
806 * @param array $queryParts Query parts array
807 * @param array $input_parameters An array of values with as many elements as there are bound parameters in the SQL statement being executed. All values are treated as \TYPO3\CMS\Core\Database\PreparedStatement::PARAM_AUTOTYPE.
808 * @return \TYPO3\CMS\Core\Database\PreparedStatement Prepared statement
809 */
810 public function prepare_SELECTqueryArray(array $queryParts, array $input_parameters = array())
811 {
812 return $this->prepare_SELECTquery($queryParts['SELECT'], $queryParts['FROM'], $queryParts['WHERE'], $queryParts['GROUPBY'], $queryParts['ORDERBY'], $queryParts['LIMIT'], $input_parameters);
813 }
814
815 /**
816 * Prepares a prepared query.
817 *
818 * @param string $query The query to execute
819 * @param array $queryComponents The components of the query to execute
820 * @return \mysqli_stmt|object MySQLi statement / DBAL object
821 * @internal This method may only be called by \TYPO3\CMS\Core\Database\PreparedStatement
822 */
823 public function prepare_PREPAREDquery($query, array $queryComponents)
824 {
825 if (!$this->isConnected) {
826 $this->connectDB();
827 }
828 $stmt = $this->link->stmt_init();
829 $success = $stmt->prepare($query);
830 if ($this->debugOutput) {
831 $this->debug('stmt_execute', $query);
832 }
833 return $success ? $stmt : null;
834 }
835
836 /**************************************
837 *
838 * Various helper functions
839 *
840 * Functions recommended to be used for
841 * - escaping values,
842 * - cleaning lists of values,
843 * - stripping of excess ORDER BY/GROUP BY keywords
844 *
845 **************************************/
846 /**
847 * Escaping and quoting values for SQL statements.
848 *
849 * @param string $str Input string
850 * @param string $table Table name for which to quote string. Just enter the table that the field-value is selected from (and any DBAL will look up which handler to use and then how to quote the string!).
851 * @param bool $allowNull Whether to allow NULL values
852 * @return string Output string; Wrapped in single quotes and quotes in the string (" / ') and \ will be backslashed (or otherwise based on DBAL handler)
853 * @see quoteStr()
854 */
855 public function fullQuoteStr($str, $table, $allowNull = false)
856 {
857 if (!$this->isConnected) {
858 $this->connectDB();
859 }
860 if ($allowNull && $str === null) {
861 return 'NULL';
862 }
863 if (is_bool($str)) {
864 $str = (int)$str;
865 }
866
867 return '\'' . $this->link->real_escape_string($str) . '\'';
868 }
869
870 /**
871 * Will fullquote all values in the one-dimensional array so they are ready to "implode" for an sql query.
872 *
873 * @param array $arr Array with values (either associative or non-associative array)
874 * @param string $table Table name for which to quote
875 * @param bool|array|string $noQuote List/array of keys NOT to quote (eg. SQL functions) - ONLY for associative arrays
876 * @param bool $allowNull Whether to allow NULL values
877 * @return array The input array with the values quoted
878 * @see cleanIntArray()
879 */
880 public function fullQuoteArray($arr, $table, $noQuote = false, $allowNull = false)
881 {
882 if (is_string($noQuote)) {
883 $noQuote = explode(',', $noQuote);
884 } elseif (!is_array($noQuote)) {
885 $noQuote = (bool)$noQuote;
886 }
887 if ($noQuote === true) {
888 return $arr;
889 }
890 foreach ($arr as $k => $v) {
891 if ($noQuote === false || !in_array($k, $noQuote)) {
892 $arr[$k] = $this->fullQuoteStr($v, $table, $allowNull);
893 }
894 }
895 return $arr;
896 }
897
898 /**
899 * Substitution for PHP function "addslashes()"
900 * Use this function instead of the PHP addslashes() function when you build queries - this will prepare your code for DBAL.
901 * NOTICE: You must wrap the output of this function in SINGLE QUOTES to be DBAL compatible. Unless you have to apply the single quotes yourself you should rather use ->fullQuoteStr()!
902 *
903 * @param string $str Input string
904 * @param string $table Table name for which to quote string. Just enter the table that the field-value is selected from (and any DBAL will look up which handler to use and then how to quote the string!).
905 * @return string Output string; Quotes (" / ') and \ will be backslashed (or otherwise based on DBAL handler)
906 * @see quoteStr()
907 */
908 public function quoteStr($str, $table)
909 {
910 if (!$this->isConnected) {
911 $this->connectDB();
912 }
913 return $this->link->real_escape_string($str);
914 }
915
916 /**
917 * Escaping values for SQL LIKE statements.
918 *
919 * @param string $str Input string
920 * @param string $table Table name for which to escape string. Just enter the table that the field-value is selected from (and any DBAL will look up which handler to use and then how to quote the string!).
921 * @return string Output string; % and _ will be escaped with \ (or otherwise based on DBAL handler)
922 * @see quoteStr()
923 */
924 public function escapeStrForLike($str, $table)
925 {
926 return addcslashes($str, '_%');
927 }
928
929 /**
930 * Will convert all values in the one-dimensional array to integers.
931 * Useful when you want to make sure an array contains only integers before imploding them in a select-list.
932 *
933 * @param array $arr Array with values
934 * @return array The input array with all values cast to (int)
935 * @see cleanIntList()
936 */
937 public function cleanIntArray($arr)
938 {
939 return array_map('intval', $arr);
940 }
941
942 /**
943 * Will force all entries in the input comma list to integers
944 * Useful when you want to make sure a commalist of supposed integers really contain only integers; You want to know that when you don't trust content that could go into an SQL statement.
945 *
946 * @param string $list List of comma-separated values which should be integers
947 * @return string The input list but with every value cast to (int)
948 * @see cleanIntArray()
949 */
950 public function cleanIntList($list)
951 {
952 return implode(',', GeneralUtility::intExplode(',', $list));
953 }
954
955 /**
956 * Removes the prefix "ORDER BY" from the input string.
957 * This function is used when you call the exec_SELECTquery() function and want to pass the ORDER BY parameter by can't guarantee that "ORDER BY" is not prefixed.
958 * Generally; This function provides a work-around to the situation where you cannot pass only the fields by which to order the result.
959 *
960 * @param string $str eg. "ORDER BY title, uid
961 * @return string eg. "title, uid
962 * @see exec_SELECTquery(), stripGroupBy()
963 */
964 public function stripOrderBy($str)
965 {
966 return preg_replace('/^(?:ORDER[[:space:]]*BY[[:space:]]*)+/i', '', trim($str));
967 }
968
969 /**
970 * Removes the prefix "GROUP BY" from the input string.
971 * This function is used when you call the SELECTquery() function and want to pass the GROUP BY parameter by can't guarantee that "GROUP BY" is not prefixed.
972 * Generally; This function provides a work-around to the situation where you cannot pass only the fields by which to order the result.
973 *
974 * @param string $str eg. "GROUP BY title, uid
975 * @return string eg. "title, uid
976 * @see exec_SELECTquery(), stripOrderBy()
977 */
978 public function stripGroupBy($str)
979 {
980 return preg_replace('/^(?:GROUP[[:space:]]*BY[[:space:]]*)+/i', '', trim($str));
981 }
982
983 /**
984 * Returns the date and time formats compatible with the given database table.
985 *
986 * @param string $table Table name for which to return an empty date. Just enter the table that the field-value is selected from (and any DBAL will look up which handler to use and then how date and time should be formatted).
987 * @return array
988 */
989 public function getDateTimeFormats($table)
990 {
991 return self::$dateTimeFormats;
992 }
993
994 /**
995 * Creates SELECT query components for selecting fields ($select) from two/three tables joined
996 * Use $mm_table together with $local_table or $foreign_table to select over two tables. Or use all three tables to select the full MM-relation.
997 * The JOIN is done with [$local_table].uid <--> [$mm_table].uid_local / [$mm_table].uid_foreign <--> [$foreign_table].uid
998 * The function is very useful for selecting MM-relations between tables adhering to the MM-format used by TCE (TYPO3 Core Engine). See the section on $GLOBALS['TCA'] in Inside TYPO3 for more details.
999 *
1000 * @param string $select See exec_SELECT_mm_query()
1001 * @param string $local_table See exec_SELECT_mm_query()
1002 * @param string $mm_table See exec_SELECT_mm_query()
1003 * @param string $foreign_table See exec_SELECT_mm_query()
1004 * @param string $whereClause See exec_SELECT_mm_query()
1005 * @param string $groupBy See exec_SELECT_mm_query()
1006 * @param string $orderBy See exec_SELECT_mm_query()
1007 * @param string $limit See exec_SELECT_mm_query()
1008 * @return array SQL query components
1009 */
1010 protected function getSelectMmQueryParts($select, $local_table, $mm_table, $foreign_table, $whereClause = '', $groupBy = '', $orderBy = '', $limit = '')
1011 {
1012 $foreign_table_as = $foreign_table == $local_table ? $foreign_table . StringUtility::getUniqueId('_join') : '';
1013 $mmWhere = $local_table ? $local_table . '.uid=' . $mm_table . '.uid_local' : '';
1014 $mmWhere .= ($local_table and $foreign_table) ? ' AND ' : '';
1015 $tables = ($local_table ? $local_table . ',' : '') . $mm_table;
1016 if ($foreign_table) {
1017 $mmWhere .= ($foreign_table_as ?: $foreign_table) . '.uid=' . $mm_table . '.uid_foreign';
1018 $tables .= ',' . $foreign_table . ($foreign_table_as ? ' AS ' . $foreign_table_as : '');
1019 }
1020 return array(
1021 'SELECT' => $select,
1022 'FROM' => $tables,
1023 'WHERE' => $mmWhere . ' ' . $whereClause,
1024 'GROUPBY' => $groupBy,
1025 'ORDERBY' => $orderBy,
1026 'LIMIT' => $limit
1027 );
1028 }
1029
1030 /**************************************
1031 *
1032 * MySQL(i) wrapper functions
1033 * (For use in your applications)
1034 *
1035 **************************************/
1036 /**
1037 * Executes query
1038 * MySQLi query() wrapper function
1039 * Beware: Use of this method should be avoided as it is experimentally supported by DBAL. You should consider
1040 * using exec_SELECTquery() and similar methods instead.
1041 *
1042 * @param string $query Query to execute
1043 * @return bool|\mysqli_result|object MySQLi result object / DBAL object
1044 */
1045 public function sql_query($query)
1046 {
1047 $res = $this->query($query);
1048 if ($this->debugOutput) {
1049 $this->debug('sql_query', $query);
1050 }
1051 return $res;
1052 }
1053
1054 /**
1055 * Returns the error status on the last query() execution
1056 *
1057 * @return string MySQLi error string.
1058 */
1059 public function sql_error()
1060 {
1061 return $this->link->error;
1062 }
1063
1064 /**
1065 * Returns the error number on the last query() execution
1066 *
1067 * @return int MySQLi error number
1068 */
1069 public function sql_errno()
1070 {
1071 return $this->link->errno;
1072 }
1073
1074 /**
1075 * Returns the number of selected rows.
1076 *
1077 * @param bool|\mysqli_result|object $res MySQLi result object / DBAL object
1078 * @return int Number of resulting rows
1079 */
1080 public function sql_num_rows($res)
1081 {
1082 if ($this->debug_check_recordset($res)) {
1083 return $res->num_rows;
1084 } else {
1085 return false;
1086 }
1087 }
1088
1089 /**
1090 * Returns an associative array that corresponds to the fetched row, or FALSE if there are no more rows.
1091 * MySQLi fetch_assoc() wrapper function
1092 *
1093 * @param bool|\mysqli_result|object $res MySQLi result object / DBAL object
1094 * @return array|bool Associative array of result row.
1095 */
1096 public function sql_fetch_assoc($res)
1097 {
1098 if ($this->debug_check_recordset($res)) {
1099 $result = $res->fetch_assoc();
1100 if ($result === null) {
1101 // Needed for compatibility
1102 $result = false;
1103 }
1104 return $result;
1105 } else {
1106 return false;
1107 }
1108 }
1109
1110 /**
1111 * Returns an array that corresponds to the fetched row, or FALSE if there are no more rows.
1112 * The array contains the values in numerical indices.
1113 * MySQLi fetch_row() wrapper function
1114 *
1115 * @param bool|\mysqli_result|object $res MySQLi result object / DBAL object
1116 * @return array|bool Array with result rows.
1117 */
1118 public function sql_fetch_row($res)
1119 {
1120 if ($this->debug_check_recordset($res)) {
1121 $result = $res->fetch_row();
1122 if ($result === null) {
1123 // Needed for compatibility
1124 $result = false;
1125 }
1126 return $result;
1127 } else {
1128 return false;
1129 }
1130 }
1131
1132 /**
1133 * Free result memory
1134 * free_result() wrapper function
1135 *
1136 * @param bool|\mysqli_result|object $res MySQLi result object / DBAL object
1137 * @return bool Returns TRUE on success or FALSE on failure.
1138 */
1139 public function sql_free_result($res)
1140 {
1141 if ($this->debug_check_recordset($res) && is_object($res)) {
1142 $res->free();
1143 return true;
1144 } else {
1145 return false;
1146 }
1147 }
1148
1149 /**
1150 * Get the ID generated from the previous INSERT operation
1151 *
1152 * @return int The uid of the last inserted record.
1153 */
1154 public function sql_insert_id()
1155 {
1156 return $this->link->insert_id;
1157 }
1158
1159 /**
1160 * Returns the number of rows affected by the last INSERT, UPDATE or DELETE query
1161 *
1162 * @return int Number of rows affected by last query
1163 */
1164 public function sql_affected_rows()
1165 {
1166 return $this->link->affected_rows;
1167 }
1168
1169 /**
1170 * Move internal result pointer
1171 *
1172 * @param bool|\mysqli_result|object $res MySQLi result object / DBAL object
1173 * @param int $seek Seek result number.
1174 * @return bool Returns TRUE on success or FALSE on failure.
1175 */
1176 public function sql_data_seek($res, $seek)
1177 {
1178 if ($this->debug_check_recordset($res)) {
1179 return $res->data_seek($seek);
1180 } else {
1181 return false;
1182 }
1183 }
1184
1185 /**
1186 * Get the type of the specified field in a result
1187 * mysql_field_type() wrapper function
1188 *
1189 * @param bool|\mysqli_result|object $res MySQLi result object / DBAL object
1190 * @param int $pointer Field index.
1191 * @return string Returns the name of the specified field index, or FALSE on error
1192 */
1193 public function sql_field_type($res, $pointer)
1194 {
1195 // mysql_field_type compatibility map
1196 // taken from: http://www.php.net/manual/en/mysqli-result.fetch-field-direct.php#89117
1197 // Constant numbers see http://php.net/manual/en/mysqli.constants.php
1198 $mysql_data_type_hash = array(
1199 1=>'tinyint',
1200 2=>'smallint',
1201 3=>'int',
1202 4=>'float',
1203 5=>'double',
1204 7=>'timestamp',
1205 8=>'bigint',
1206 9=>'mediumint',
1207 10=>'date',
1208 11=>'time',
1209 12=>'datetime',
1210 13=>'year',
1211 16=>'bit',
1212 //252 is currently mapped to all text and blob types (MySQL 5.0.51a)
1213 253=>'varchar',
1214 254=>'char',
1215 246=>'decimal'
1216 );
1217 if ($this->debug_check_recordset($res)) {
1218 $metaInfo = $res->fetch_field_direct($pointer);
1219 if ($metaInfo === false) {
1220 return false;
1221 }
1222 return $mysql_data_type_hash[$metaInfo->type];
1223 } else {
1224 return false;
1225 }
1226 }
1227
1228 /**
1229 * Open a (persistent) connection to a MySQL server
1230 *
1231 * @return bool|void
1232 * @throws \RuntimeException
1233 */
1234 public function sql_pconnect()
1235 {
1236 if ($this->isConnected) {
1237 return $this->link;
1238 }
1239
1240 if (!extension_loaded('mysqli')) {
1241 throw new \RuntimeException(
1242 'Database Error: PHP mysqli extension not loaded. This is a must have for TYPO3 CMS!',
1243 1271492607
1244 );
1245 }
1246
1247 $host = $this->persistentDatabaseConnection
1248 ? 'p:' . $this->databaseHost
1249 : $this->databaseHost;
1250
1251 $this->link = mysqli_init();
1252 $connected = $this->link->real_connect(
1253 $host,
1254 $this->databaseUsername,
1255 $this->databaseUserPassword,
1256 null,
1257 (int)$this->databasePort,
1258 $this->databaseSocket,
1259 $this->connectionCompression ? MYSQLI_CLIENT_COMPRESS : 0
1260 );
1261
1262 if ($connected) {
1263 $this->isConnected = true;
1264
1265 if ($this->link->set_charset($this->connectionCharset) === false) {
1266 GeneralUtility::sysLog(
1267 'Error setting connection charset to "' . $this->connectionCharset . '"',
1268 'core',
1269 GeneralUtility::SYSLOG_SEVERITY_ERROR
1270 );
1271 }
1272
1273 foreach ($this->initializeCommandsAfterConnect as $command) {
1274 if ($this->query($command) === false) {
1275 GeneralUtility::sysLog(
1276 'Could not initialize DB connection with query "' . $command . '": ' . $this->sql_error(),
1277 'core',
1278 GeneralUtility::SYSLOG_SEVERITY_ERROR
1279 );
1280 }
1281 }
1282 $this->checkConnectionCharset();
1283 } else {
1284 // @todo This should raise an exception. Would be useful especially to work during installation.
1285 $error_msg = $this->link->connect_error;
1286 $this->link = null;
1287 GeneralUtility::sysLog(
1288 'Could not connect to MySQL server ' . $host . ' with user ' . $this->databaseUsername . ': ' . $error_msg,
1289 'core',
1290 GeneralUtility::SYSLOG_SEVERITY_FATAL
1291 );
1292 }
1293 return $this->link;
1294 }
1295
1296 /**
1297 * Select a SQL database
1298 *
1299 * @return bool Returns TRUE on success or FALSE on failure.
1300 */
1301 public function sql_select_db()
1302 {
1303 if (!$this->isConnected) {
1304 $this->connectDB();
1305 }
1306
1307 $ret = $this->link->select_db($this->databaseName);
1308 if (!$ret) {
1309 GeneralUtility::sysLog(
1310 'Could not select MySQL database ' . $this->databaseName . ': ' . $this->sql_error(),
1311 'core',
1312 GeneralUtility::SYSLOG_SEVERITY_FATAL
1313 );
1314 }
1315 return $ret;
1316 }
1317
1318 /**************************************
1319 *
1320 * SQL admin functions
1321 * (For use in the Install Tool and Extension Manager)
1322 *
1323 **************************************/
1324 /**
1325 * Listing databases from current MySQL connection. NOTICE: It WILL try to select those databases and thus break selection of current database.
1326 * This is only used as a service function in the (1-2-3 process) of the Install Tool.
1327 * In any case a lookup should be done in the _DEFAULT handler DBMS then.
1328 * Use in Install Tool only!
1329 *
1330 * @return array Each entry represents a database name
1331 * @throws \RuntimeException
1332 */
1333 public function admin_get_dbs()
1334 {
1335 $dbArr = array();
1336 $db_list = $this->query('SELECT SCHEMA_NAME FROM information_schema.SCHEMATA');
1337 if ($db_list === false) {
1338 throw new \RuntimeException(
1339 'MySQL Error: Cannot get tablenames: "' . $this->sql_error() . '"!',
1340 1378457171
1341 );
1342 } else {
1343 while ($row = $db_list->fetch_object()) {
1344 try {
1345 $this->setDatabaseName($row->SCHEMA_NAME);
1346 if ($this->sql_select_db()) {
1347 $dbArr[] = $row->SCHEMA_NAME;
1348 }
1349 } catch (\RuntimeException $exception) {
1350 // The exception happens if we cannot connect to the database
1351 // (usually due to missing permissions). This is ok here.
1352 // We catch the exception, skip the database and continue.
1353 }
1354 }
1355 }
1356 return $dbArr;
1357 }
1358
1359 /**
1360 * Returns the list of tables from the default database, TYPO3_db (quering the DBMS)
1361 * In a DBAL this method should 1) look up all tables from the DBMS of
1362 * the _DEFAULT handler and then 2) add all tables *configured* to be managed by other handlers
1363 *
1364 * @return array Array with tablenames as key and arrays with status information as value
1365 */
1366 public function admin_get_tables()
1367 {
1368 $whichTables = array();
1369 $tables_result = $this->query('SHOW TABLE STATUS FROM `' . $this->databaseName . '`');
1370 if ($tables_result !== false) {
1371 while ($theTable = $tables_result->fetch_assoc()) {
1372 $whichTables[$theTable['Name']] = $theTable;
1373 }
1374 $tables_result->free();
1375 }
1376 return $whichTables;
1377 }
1378
1379 /**
1380 * Returns information about each field in the $table (quering the DBMS)
1381 * In a DBAL this should look up the right handler for the table and return compatible information
1382 * This function is important not only for the Install Tool but probably for
1383 * DBALs as well since they might need to look up table specific information
1384 * in order to construct correct queries. In such cases this information should
1385 * probably be cached for quick delivery.
1386 *
1387 * @param string $tableName Table name
1388 * @return array Field information in an associative array with fieldname => field row
1389 */
1390 public function admin_get_fields($tableName)
1391 {
1392 $output = array();
1393 $columns_res = $this->query('SHOW FULL COLUMNS FROM `' . $tableName . '`');
1394 if ($columns_res !== false) {
1395 while ($fieldRow = $columns_res->fetch_assoc()) {
1396 $output[$fieldRow['Field']] = $fieldRow;
1397 }
1398 $columns_res->free();
1399 }
1400 return $output;
1401 }
1402
1403 /**
1404 * Returns information about each index key in the $table (quering the DBMS)
1405 * In a DBAL this should look up the right handler for the table and return compatible information
1406 *
1407 * @param string $tableName Table name
1408 * @return array Key information in a numeric array
1409 */
1410 public function admin_get_keys($tableName)
1411 {
1412 $output = array();
1413 $keyRes = $this->query('SHOW KEYS FROM `' . $tableName . '`');
1414 if ($keyRes !== false) {
1415 while ($keyRow = $keyRes->fetch_assoc()) {
1416 $output[] = $keyRow;
1417 }
1418 $keyRes->free();
1419 }
1420 return $output;
1421 }
1422
1423 /**
1424 * Returns information about the character sets supported by the current DBM
1425 * This function is important not only for the Install Tool but probably for
1426 * DBALs as well since they might need to look up table specific information
1427 * in order to construct correct queries. In such cases this information should
1428 * probably be cached for quick delivery.
1429 *
1430 * This is used by the Install Tool to convert tables with non-UTF8 charsets
1431 * Use in Install Tool only!
1432 *
1433 * @return array Array with Charset as key and an array of "Charset", "Description", "Default collation", "Maxlen" as values
1434 */
1435 public function admin_get_charsets()
1436 {
1437 $output = array();
1438 $columns_res = $this->query('SHOW CHARACTER SET');
1439 if ($columns_res !== false) {
1440 while ($row = $columns_res->fetch_assoc()) {
1441 $output[$row['Charset']] = $row;
1442 }
1443 $columns_res->free();
1444 }
1445 return $output;
1446 }
1447
1448 /**
1449 * mysqli() wrapper function, used by the Install Tool and EM for all queries regarding management of the database!
1450 *
1451 * @param string $query Query to execute
1452 * @return bool|\mysqli_result|object MySQLi result object / DBAL object
1453 */
1454 public function admin_query($query)
1455 {
1456 $res = $this->query($query);
1457 if ($this->debugOutput) {
1458 $this->debug('admin_query', $query);
1459 }
1460 return $res;
1461 }
1462
1463 /******************************
1464 *
1465 * Connect handling
1466 *
1467 ******************************/
1468
1469 /**
1470 * Set database host
1471 *
1472 * @param string $host
1473 */
1474 public function setDatabaseHost($host = 'localhost')
1475 {
1476 $this->disconnectIfConnected();
1477 $this->databaseHost = $host;
1478 }
1479
1480 /**
1481 * Set database port
1482 *
1483 * @param int $port
1484 */
1485 public function setDatabasePort($port = 3306)
1486 {
1487 $this->disconnectIfConnected();
1488 $this->databasePort = (int)$port;
1489 }
1490
1491 /**
1492 * Set database socket
1493 *
1494 * @param string|NULL $socket
1495 */
1496 public function setDatabaseSocket($socket = null)
1497 {
1498 $this->disconnectIfConnected();
1499 $this->databaseSocket = $socket;
1500 }
1501
1502 /**
1503 * Set database name
1504 *
1505 * @param string $name
1506 */
1507 public function setDatabaseName($name)
1508 {
1509 $this->disconnectIfConnected();
1510 $this->databaseName = $name;
1511 }
1512
1513 /**
1514 * Set database username
1515 *
1516 * @param string $username
1517 */
1518 public function setDatabaseUsername($username)
1519 {
1520 $this->disconnectIfConnected();
1521 $this->databaseUsername = $username;
1522 }
1523
1524 /**
1525 * Set database password
1526 *
1527 * @param string $password
1528 */
1529 public function setDatabasePassword($password)
1530 {
1531 $this->disconnectIfConnected();
1532 $this->databaseUserPassword = $password;
1533 }
1534
1535 /**
1536 * Set persistent database connection
1537 *
1538 * @param bool $persistentDatabaseConnection
1539 * @see http://php.net/manual/de/mysqli.persistconns.php
1540 */
1541 public function setPersistentDatabaseConnection($persistentDatabaseConnection)
1542 {
1543 $this->disconnectIfConnected();
1544 $this->persistentDatabaseConnection = (bool)$persistentDatabaseConnection;
1545 }
1546
1547 /**
1548 * Set connection compression. Might be an advantage, if SQL server is not on localhost
1549 *
1550 * @param bool $connectionCompression TRUE if connection should be compressed
1551 */
1552 public function setConnectionCompression($connectionCompression)
1553 {
1554 $this->disconnectIfConnected();
1555 $this->connectionCompression = (bool)$connectionCompression;
1556 }
1557
1558 /**
1559 * Set commands to be fired after connection was established
1560 *
1561 * @param array $commands List of SQL commands to be executed after connect
1562 */
1563 public function setInitializeCommandsAfterConnect(array $commands)
1564 {
1565 $this->disconnectIfConnected();
1566 $this->initializeCommandsAfterConnect = $commands;
1567 }
1568
1569 /**
1570 * Set the charset that should be used for the MySQL connection.
1571 * The given value will be passed on to mysqli_set_charset().
1572 *
1573 * The default value of this setting is utf8.
1574 *
1575 * @param string $connectionCharset The connection charset that will be passed on to mysqli_set_charset() when connecting the database. Default is utf8.
1576 * @return void
1577 */
1578 public function setConnectionCharset($connectionCharset = 'utf8')
1579 {
1580 $this->disconnectIfConnected();
1581 $this->connectionCharset = $connectionCharset;
1582 }
1583
1584 /**
1585 * Connects to database for TYPO3 sites:
1586 *
1587 * @throws \RuntimeException
1588 * @throws \UnexpectedValueException
1589 * @return void
1590 */
1591 public function connectDB()
1592 {
1593 // Early return if connected already
1594 if ($this->isConnected) {
1595 return;
1596 }
1597
1598 if (!$this->databaseName) {
1599 throw new \RuntimeException(
1600 'TYPO3 Fatal Error: No database selected!',
1601 1270853882
1602 );
1603 }
1604
1605 if ($this->sql_pconnect()) {
1606 if (!$this->sql_select_db()) {
1607 throw new \RuntimeException(
1608 'TYPO3 Fatal Error: Cannot connect to the current database, "' . $this->databaseName . '"!',
1609 1270853883
1610 );
1611 }
1612 } else {
1613 throw new \RuntimeException(
1614 'TYPO3 Fatal Error: The current username, password or host was not accepted when the connection to the database was attempted to be established!',
1615 1270853884
1616 );
1617 }
1618
1619 // Prepare user defined objects (if any) for hooks which extend query methods
1620 $this->preProcessHookObjects = array();
1621 $this->postProcessHookObjects = array();
1622 if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_db.php']['queryProcessors'])) {
1623 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_db.php']['queryProcessors'] as $classRef) {
1624 $hookObject = GeneralUtility::getUserObj($classRef);
1625 if (!(
1626 $hookObject instanceof PreProcessQueryHookInterface
1627 || $hookObject instanceof PostProcessQueryHookInterface
1628 )) {
1629 throw new \UnexpectedValueException(
1630 '$hookObject must either implement interface TYPO3\\CMS\\Core\\Database\\PreProcessQueryHookInterface or interface TYPO3\\CMS\\Core\\Database\\PostProcessQueryHookInterface',
1631 1299158548
1632 );
1633 }
1634 if ($hookObject instanceof PreProcessQueryHookInterface) {
1635 $this->preProcessHookObjects[] = $hookObject;
1636 }
1637 if ($hookObject instanceof PostProcessQueryHookInterface) {
1638 $this->postProcessHookObjects[] = $hookObject;
1639 }
1640 }
1641 }
1642 }
1643
1644 /**
1645 * Checks if database is connected
1646 *
1647 * @return bool
1648 */
1649 public function isConnected()
1650 {
1651 // We think we're still connected
1652 if ($this->isConnected) {
1653 // Check if this is really the case or if the database server has gone away for some reason
1654 // Using mysqlnd ping() does not reconnect (which we would not want anyway since charset etc would not be reinitialized that way)
1655 $this->isConnected = $this->link->ping();
1656 }
1657 return $this->isConnected;
1658 }
1659
1660 /**
1661 * Checks if the current connection character set has the same value
1662 * as the connectionCharset variable.
1663 *
1664 * To determine the character set these MySQL session variables are
1665 * checked: character_set_client, character_set_results and
1666 * character_set_connection.
1667 *
1668 * If the character set does not match or if the session variables
1669 * can not be read a RuntimeException is thrown.
1670 *
1671 * @return void
1672 * @throws \RuntimeException
1673 */
1674 protected function checkConnectionCharset()
1675 {
1676 $sessionResult = $this->sql_query('SHOW SESSION VARIABLES LIKE \'character_set%\'');
1677
1678 if ($sessionResult === false) {
1679 GeneralUtility::sysLog(
1680 'Error while retrieving the current charset session variables from the database: ' . $this->sql_error(),
1681 'core',
1682 GeneralUtility::SYSLOG_SEVERITY_ERROR
1683 );
1684 throw new \RuntimeException(
1685 'TYPO3 Fatal Error: Could not determine the current charset of the database.',
1686 1381847136
1687 );
1688 }
1689
1690 $charsetVariables = array();
1691 while (($row = $this->sql_fetch_row($sessionResult)) !== false) {
1692 $variableName = $row[0];
1693 $variableValue = $row[1];
1694 $charsetVariables[$variableName] = $variableValue;
1695 }
1696 $this->sql_free_result($sessionResult);
1697
1698 // These variables are set with the "Set names" command which was
1699 // used in the past. This is why we check them.
1700 $charsetRequiredVariables = array(
1701 'character_set_client',
1702 'character_set_results',
1703 'character_set_connection',
1704 );
1705
1706 $hasValidCharset = true;
1707 foreach ($charsetRequiredVariables as $variableName) {
1708 if (empty($charsetVariables[$variableName])) {
1709 GeneralUtility::sysLog(
1710 'A required session variable is missing in the current MySQL connection: ' . $variableName,
1711 'core',
1712 GeneralUtility::SYSLOG_SEVERITY_ERROR
1713 );
1714 throw new \RuntimeException(
1715 'TYPO3 Fatal Error: Could not determine the value of the database session variable: ' . $variableName,
1716 1381847779
1717 );
1718 }
1719
1720 if ($charsetVariables[$variableName] !== $this->connectionCharset) {
1721 $hasValidCharset = false;
1722 break;
1723 }
1724 }
1725
1726 if (!$hasValidCharset) {
1727 throw new \RuntimeException(
1728 'It looks like the character set ' . $this->connectionCharset . ' is not used for this connection even though it is configured as connection charset. ' .
1729 'This TYPO3 installation is using the $GLOBALS[\'TYPO3_CONF_VARS\'][\'SYS\'][\'setDBinit\'] property with the following value: "' .
1730 $GLOBALS['TYPO3_CONF_VARS']['SYS']['setDBinit'] . '". Please make sure that this command does not overwrite the configured charset. ' .
1731 'Please note that for the TYPO3 database everything other than utf8 is unsupported since version 4.7.',
1732 1389697515
1733 );
1734 }
1735 }
1736
1737 /**
1738 * Disconnect from database if connected
1739 *
1740 * @return void
1741 */
1742 protected function disconnectIfConnected()
1743 {
1744 if ($this->isConnected) {
1745 $this->link->close();
1746 $this->isConnected = false;
1747 }
1748 }
1749
1750 /**
1751 * Returns current database handle
1752 *
1753 * @return \mysqli|NULL
1754 */
1755 public function getDatabaseHandle()
1756 {
1757 return $this->link;
1758 }
1759
1760 /**
1761 * Set current database handle, usually \mysqli
1762 *
1763 * @param \mysqli $handle
1764 */
1765 public function setDatabaseHandle($handle)
1766 {
1767 $this->link = $handle;
1768 }
1769
1770 /**
1771 * Get the MySQL server version
1772 *
1773 * @return string
1774 */
1775 public function getServerVersion()
1776 {
1777 return $this->link->server_info;
1778 }
1779
1780 /******************************
1781 *
1782 * Debugging
1783 *
1784 ******************************/
1785 /**
1786 * Debug function: Outputs error if any
1787 *
1788 * @param string $func Function calling debug()
1789 * @param string $query Last query if not last built query
1790 * @return void
1791 */
1792 public function debug($func, $query = '')
1793 {
1794 $error = $this->sql_error();
1795 if ($error || (int)$this->debugOutput === 2) {
1796 \TYPO3\CMS\Core\Utility\DebugUtility::debug(
1797 array(
1798 'caller' => \TYPO3\CMS\Core\Database\DatabaseConnection::class . '::' . $func,
1799 'ERROR' => $error,
1800 'lastBuiltQuery' => $query ? $query : $this->debug_lastBuiltQuery,
1801 'debug_backtrace' => \TYPO3\CMS\Core\Utility\DebugUtility::debugTrail()
1802 ),
1803 $func,
1804 is_object($GLOBALS['error']) && @is_callable(array($GLOBALS['error'], 'debug'))
1805 ? ''
1806 : 'DB Error'
1807 );
1808 }
1809 }
1810
1811 /**
1812 * Checks if record set is valid and writes debugging information into devLog if not.
1813 *
1814 * @param bool|\mysqli_result|object MySQLi result object / DBAL object
1815 * @return bool TRUE if the record set is valid, FALSE otherwise
1816 */
1817 public function debug_check_recordset($res)
1818 {
1819 if ($res !== false) {
1820 return true;
1821 }
1822 $trace = debug_backtrace(0);
1823 array_shift($trace);
1824 $msg = 'Invalid database result detected: function TYPO3\\CMS\\Core\\Database\\DatabaseConnection->'
1825 . $trace[0]['function'] . ' called from file ' . substr($trace[0]['file'], (strlen(PATH_site) + 2))
1826 . ' in line ' . $trace[0]['line'] . '.';
1827 GeneralUtility::sysLog(
1828 $msg . ' Use a devLog extension to get more details.',
1829 'core',
1830 GeneralUtility::SYSLOG_SEVERITY_ERROR
1831 );
1832 // Send to devLog if enabled
1833 if (TYPO3_DLOG) {
1834 $debugLogData = array(
1835 'SQL Error' => $this->sql_error(),
1836 'Backtrace' => $trace
1837 );
1838 if ($this->debug_lastBuiltQuery) {
1839 $debugLogData = array('SQL Query' => $this->debug_lastBuiltQuery) + $debugLogData;
1840 }
1841 GeneralUtility::devLog($msg, 'Core/t3lib_db', 3, $debugLogData);
1842 }
1843 return false;
1844 }
1845
1846 /**
1847 * Explain select queries
1848 * If $this->explainOutput is set, SELECT queries will be explained here. Only queries with more than one possible result row will be displayed.
1849 * The output is either printed as raw HTML output or embedded into the TS admin panel (checkbox must be enabled!)
1850 *
1851 * @todo Feature is not DBAL-compliant
1852 *
1853 * @param string $query SQL query
1854 * @param string $from_table Table(s) from which to select. This is what comes right after "FROM ...". Required value.
1855 * @param int $row_count Number of resulting rows
1856 * @return bool TRUE if explain was run, FALSE otherwise
1857 */
1858 protected function explain($query, $from_table, $row_count)
1859 {
1860 $debugAllowedForIp = GeneralUtility::cmpIP(
1861 GeneralUtility::getIndpEnv('REMOTE_ADDR'),
1862 $GLOBALS['TYPO3_CONF_VARS']['SYS']['devIPmask']
1863 );
1864 if (
1865 (int)$this->explainOutput == 1
1866 || ((int)$this->explainOutput == 2 && $debugAllowedForIp)
1867 ) {
1868 // Raw HTML output
1869 $explainMode = 1;
1870 } elseif ((int)$this->explainOutput == 3) {
1871 // Embed the output into the TS admin panel
1872 $explainMode = 2;
1873 } else {
1874 return false;
1875 }
1876 $error = $this->sql_error();
1877 $trail = \TYPO3\CMS\Core\Utility\DebugUtility::debugTrail();
1878 $explain_tables = array();
1879 $explain_output = array();
1880 $res = $this->sql_query('EXPLAIN ' . $query, $this->link);
1881 if (is_a($res, '\\mysqli_result')) {
1882 while ($tempRow = $this->sql_fetch_assoc($res)) {
1883 $explain_output[] = $tempRow;
1884 $explain_tables[] = $tempRow['table'];
1885 }
1886 $this->sql_free_result($res);
1887 }
1888 $indices_output = array();
1889 // Notice: Rows are skipped if there is only one result, or if no conditions are set
1890 if ($explain_output[0]['rows'] > 1 || $explain_output[0]['type'] === 'ALL') {
1891 // Only enable output if it's really useful
1892 $debug = true;
1893 foreach ($explain_tables as $table) {
1894 $tableRes = $this->sql_query('SHOW TABLE STATUS LIKE \'' . $table . '\'');
1895 $isTable = $this->sql_num_rows($tableRes);
1896 if ($isTable) {
1897 $res = $this->sql_query('SHOW INDEX FROM ' . $table, $this->link);
1898 if (is_a($res, '\\mysqli_result')) {
1899 while ($tempRow = $this->sql_fetch_assoc($res)) {
1900 $indices_output[] = $tempRow;
1901 }
1902 $this->sql_free_result($res);
1903 }
1904 }
1905 $this->sql_free_result($tableRes);
1906 }
1907 } else {
1908 $debug = false;
1909 }
1910 if ($debug) {
1911 if ($explainMode) {
1912 $data = array();
1913 $data['query'] = $query;
1914 $data['trail'] = $trail;
1915 $data['row_count'] = $row_count;
1916 if ($error) {
1917 $data['error'] = $error;
1918 }
1919 if (!empty($explain_output)) {
1920 $data['explain'] = $explain_output;
1921 }
1922 if (!empty($indices_output)) {
1923 $data['indices'] = $indices_output;
1924 }
1925 if ($explainMode == 1) {
1926 \TYPO3\CMS\Core\Utility\DebugUtility::debug($data, 'Tables: ' . $from_table, 'DB SQL EXPLAIN');
1927 } elseif ($explainMode == 2) {
1928 /** @var TimeTracker $timeTracker */
1929 $timeTracker = GeneralUtility::makeInstance(TimeTracker::class);
1930 $timeTracker->setTSselectQuery($data);
1931 }
1932 }
1933 return true;
1934 }
1935 return false;
1936 }
1937
1938 /**
1939 * Serialize destructs current connection
1940 *
1941 * @return array All protected properties that should be saved
1942 */
1943 public function __sleep()
1944 {
1945 $this->disconnectIfConnected();
1946 return array(
1947 'debugOutput',
1948 'explainOutput',
1949 'databaseHost',
1950 'databasePort',
1951 'databaseSocket',
1952 'databaseName',
1953 'databaseUsername',
1954 'databaseUserPassword',
1955 'persistentDatabaseConnection',
1956 'connectionCompression',
1957 'initializeCommandsAfterConnect',
1958 'default_charset',
1959 );
1960 }
1961 }