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