cfbd7dcbc93f401e5ffd104c023137026ce504d9
[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 // We are not using the TYPO3 CMS shim here as the database parameters in this class
1252 // are settable externally. This requires building the connection parameter array
1253 // just in time when establishing the connection.
1254 $connection = \Doctrine\DBAL\DriverManager::getConnection([
1255 'driver' => 'mysqli',
1256 'wrapperClass' => Connection::class,
1257 'host' => $host,
1258 'port' => (int)$this->databasePort,
1259 'unix_socket' => $this->databaseSocket,
1260 'user' => $this->databaseUsername,
1261 'password' => $this->databaseUserPassword,
1262 'charset' => $this->connectionCharset,
1263 ]);
1264
1265 // Mimic the previous behavior of returning false on connection errors
1266 try {
1267 /** @var \Doctrine\DBAL\Driver\Mysqli\MysqliConnection $mysqliConnection */
1268 $mysqliConnection = $connection->getWrappedConnection();
1269 $this->link = $mysqliConnection->getWrappedResourceHandle();
1270 } catch (\Doctrine\DBAL\Exception\ConnectionException $exception) {
1271 return false;
1272 }
1273
1274 if ($connection->isConnected()) {
1275 $this->isConnected = true;
1276
1277 foreach ($this->initializeCommandsAfterConnect as $command) {
1278 if ($this->query($command) === false) {
1279 GeneralUtility::sysLog(
1280 'Could not initialize DB connection with query "' . $command . '": ' . $this->sql_error(),
1281 'core',
1282 GeneralUtility::SYSLOG_SEVERITY_ERROR
1283 );
1284 }
1285 }
1286 $this->checkConnectionCharset();
1287 } else {
1288 // @todo This should raise an exception. Would be useful especially to work during installation.
1289 $error_msg = $this->link->connect_error;
1290 $this->link = null;
1291 GeneralUtility::sysLog(
1292 'Could not connect to MySQL server ' . $host . ' with user ' . $this->databaseUsername . ': '
1293 . $error_msg,
1294 'core',
1295 GeneralUtility::SYSLOG_SEVERITY_FATAL
1296 );
1297 }
1298
1299 return $this->link;
1300 }
1301
1302 /**
1303 * Select a SQL database
1304 *
1305 * @return bool Returns TRUE on success or FALSE on failure.
1306 */
1307 public function sql_select_db()
1308 {
1309 if (!$this->isConnected) {
1310 $this->connectDB();
1311 }
1312
1313 $ret = $this->link->select_db($this->databaseName);
1314 if (!$ret) {
1315 GeneralUtility::sysLog(
1316 'Could not select MySQL database ' . $this->databaseName . ': ' . $this->sql_error(),
1317 'core',
1318 GeneralUtility::SYSLOG_SEVERITY_FATAL
1319 );
1320 }
1321 return $ret;
1322 }
1323
1324 /**************************************
1325 *
1326 * SQL admin functions
1327 * (For use in the Install Tool and Extension Manager)
1328 *
1329 **************************************/
1330 /**
1331 * Listing databases from current MySQL connection. NOTICE: It WILL try to select those databases and thus break selection of current database.
1332 * This is only used as a service function in the (1-2-3 process) of the Install Tool.
1333 * In any case a lookup should be done in the _DEFAULT handler DBMS then.
1334 * Use in Install Tool only!
1335 *
1336 * @return array Each entry represents a database name
1337 * @throws \RuntimeException
1338 */
1339 public function admin_get_dbs()
1340 {
1341 $dbArr = array();
1342 $db_list = $this->query('SELECT SCHEMA_NAME FROM information_schema.SCHEMATA');
1343 if ($db_list === false) {
1344 throw new \RuntimeException(
1345 'MySQL Error: Cannot get tablenames: "' . $this->sql_error() . '"!',
1346 1378457171
1347 );
1348 } else {
1349 while ($row = $db_list->fetch_object()) {
1350 try {
1351 $this->setDatabaseName($row->SCHEMA_NAME);
1352 if ($this->sql_select_db()) {
1353 $dbArr[] = $row->SCHEMA_NAME;
1354 }
1355 } catch (\RuntimeException $exception) {
1356 // The exception happens if we cannot connect to the database
1357 // (usually due to missing permissions). This is ok here.
1358 // We catch the exception, skip the database and continue.
1359 }
1360 }
1361 }
1362 return $dbArr;
1363 }
1364
1365 /**
1366 * Returns the list of tables from the default database, TYPO3_db (quering the DBMS)
1367 * In a DBAL this method should 1) look up all tables from the DBMS of
1368 * the _DEFAULT handler and then 2) add all tables *configured* to be managed by other handlers
1369 *
1370 * @return array Array with tablenames as key and arrays with status information as value
1371 */
1372 public function admin_get_tables()
1373 {
1374 $whichTables = array();
1375 $tables_result = $this->query('SHOW TABLE STATUS FROM `' . $this->databaseName . '`');
1376 if ($tables_result !== false) {
1377 while ($theTable = $tables_result->fetch_assoc()) {
1378 $whichTables[$theTable['Name']] = $theTable;
1379 }
1380 $tables_result->free();
1381 }
1382 return $whichTables;
1383 }
1384
1385 /**
1386 * Returns information about each field in the $table (quering the DBMS)
1387 * In a DBAL this should look up the right handler for the table and return compatible information
1388 * This function is important not only for the Install Tool but probably for
1389 * DBALs as well since they might need to look up table specific information
1390 * in order to construct correct queries. In such cases this information should
1391 * probably be cached for quick delivery.
1392 *
1393 * @param string $tableName Table name
1394 * @return array Field information in an associative array with fieldname => field row
1395 */
1396 public function admin_get_fields($tableName)
1397 {
1398 $output = array();
1399 $columns_res = $this->query('SHOW FULL COLUMNS FROM `' . $tableName . '`');
1400 if ($columns_res !== false) {
1401 while ($fieldRow = $columns_res->fetch_assoc()) {
1402 $output[$fieldRow['Field']] = $fieldRow;
1403 }
1404 $columns_res->free();
1405 }
1406 return $output;
1407 }
1408
1409 /**
1410 * Returns information about each index key in the $table (quering the DBMS)
1411 * In a DBAL this should look up the right handler for the table and return compatible information
1412 *
1413 * @param string $tableName Table name
1414 * @return array Key information in a numeric array
1415 */
1416 public function admin_get_keys($tableName)
1417 {
1418 $output = array();
1419 $keyRes = $this->query('SHOW KEYS FROM `' . $tableName . '`');
1420 if ($keyRes !== false) {
1421 while ($keyRow = $keyRes->fetch_assoc()) {
1422 $output[] = $keyRow;
1423 }
1424 $keyRes->free();
1425 }
1426 return $output;
1427 }
1428
1429 /**
1430 * Returns information about the character sets supported by the current DBM
1431 * This function is important not only for the Install Tool but probably for
1432 * DBALs as well since they might need to look up table specific information
1433 * in order to construct correct queries. In such cases this information should
1434 * probably be cached for quick delivery.
1435 *
1436 * This is used by the Install Tool to convert tables with non-UTF8 charsets
1437 * Use in Install Tool only!
1438 *
1439 * @return array Array with Charset as key and an array of "Charset", "Description", "Default collation", "Maxlen" as values
1440 */
1441 public function admin_get_charsets()
1442 {
1443 $output = array();
1444 $columns_res = $this->query('SHOW CHARACTER SET');
1445 if ($columns_res !== false) {
1446 while ($row = $columns_res->fetch_assoc()) {
1447 $output[$row['Charset']] = $row;
1448 }
1449 $columns_res->free();
1450 }
1451 return $output;
1452 }
1453
1454 /**
1455 * mysqli() wrapper function, used by the Install Tool and EM for all queries regarding management of the database!
1456 *
1457 * @param string $query Query to execute
1458 * @return bool|\mysqli_result|object MySQLi result object / DBAL object
1459 */
1460 public function admin_query($query)
1461 {
1462 $res = $this->query($query);
1463 if ($this->debugOutput) {
1464 $this->debug('admin_query', $query);
1465 }
1466 return $res;
1467 }
1468
1469 /******************************
1470 *
1471 * Connect handling
1472 *
1473 ******************************/
1474
1475 /**
1476 * Set database host
1477 *
1478 * @param string $host
1479 */
1480 public function setDatabaseHost($host = 'localhost')
1481 {
1482 $this->disconnectIfConnected();
1483 $this->databaseHost = $host;
1484 }
1485
1486 /**
1487 * Set database port
1488 *
1489 * @param int $port
1490 */
1491 public function setDatabasePort($port = 3306)
1492 {
1493 $this->disconnectIfConnected();
1494 $this->databasePort = (int)$port;
1495 }
1496
1497 /**
1498 * Set database socket
1499 *
1500 * @param string|NULL $socket
1501 */
1502 public function setDatabaseSocket($socket = null)
1503 {
1504 $this->disconnectIfConnected();
1505 $this->databaseSocket = $socket;
1506 }
1507
1508 /**
1509 * Set database name
1510 *
1511 * @param string $name
1512 */
1513 public function setDatabaseName($name)
1514 {
1515 $this->disconnectIfConnected();
1516 $this->databaseName = $name;
1517 }
1518
1519 /**
1520 * Set database username
1521 *
1522 * @param string $username
1523 */
1524 public function setDatabaseUsername($username)
1525 {
1526 $this->disconnectIfConnected();
1527 $this->databaseUsername = $username;
1528 }
1529
1530 /**
1531 * Set database password
1532 *
1533 * @param string $password
1534 */
1535 public function setDatabasePassword($password)
1536 {
1537 $this->disconnectIfConnected();
1538 $this->databaseUserPassword = $password;
1539 }
1540
1541 /**
1542 * Set persistent database connection
1543 *
1544 * @param bool $persistentDatabaseConnection
1545 * @see http://php.net/manual/de/mysqli.persistconns.php
1546 */
1547 public function setPersistentDatabaseConnection($persistentDatabaseConnection)
1548 {
1549 $this->disconnectIfConnected();
1550 $this->persistentDatabaseConnection = (bool)$persistentDatabaseConnection;
1551 }
1552
1553 /**
1554 * Set connection compression. Might be an advantage, if SQL server is not on localhost
1555 *
1556 * @param bool $connectionCompression TRUE if connection should be compressed
1557 */
1558 public function setConnectionCompression($connectionCompression)
1559 {
1560 $this->disconnectIfConnected();
1561 $this->connectionCompression = (bool)$connectionCompression;
1562 }
1563
1564 /**
1565 * Set commands to be fired after connection was established
1566 *
1567 * @param array $commands List of SQL commands to be executed after connect
1568 */
1569 public function setInitializeCommandsAfterConnect(array $commands)
1570 {
1571 $this->disconnectIfConnected();
1572 $this->initializeCommandsAfterConnect = $commands;
1573 }
1574
1575 /**
1576 * Set the charset that should be used for the MySQL connection.
1577 * The given value will be passed on to mysqli_set_charset().
1578 *
1579 * The default value of this setting is utf8.
1580 *
1581 * @param string $connectionCharset The connection charset that will be passed on to mysqli_set_charset() when connecting the database. Default is utf8.
1582 * @return void
1583 */
1584 public function setConnectionCharset($connectionCharset = 'utf8')
1585 {
1586 $this->disconnectIfConnected();
1587 $this->connectionCharset = $connectionCharset;
1588 }
1589
1590 /**
1591 * Connects to database for TYPO3 sites:
1592 *
1593 * @throws \RuntimeException
1594 * @throws \UnexpectedValueException
1595 * @return void
1596 */
1597 public function connectDB()
1598 {
1599 // Early return if connected already
1600 if ($this->isConnected) {
1601 return;
1602 }
1603
1604 if (!$this->databaseName) {
1605 throw new \RuntimeException(
1606 'TYPO3 Fatal Error: No database selected!',
1607 1270853882
1608 );
1609 }
1610
1611 if ($this->sql_pconnect()) {
1612 if (!$this->sql_select_db()) {
1613 throw new \RuntimeException(
1614 'TYPO3 Fatal Error: Cannot connect to the current database, "' . $this->databaseName . '"!',
1615 1270853883
1616 );
1617 }
1618 } else {
1619 throw new \RuntimeException(
1620 'TYPO3 Fatal Error: The current username, password or host was not accepted when the connection to the database was attempted to be established!',
1621 1270853884
1622 );
1623 }
1624
1625 // Prepare user defined objects (if any) for hooks which extend query methods
1626 $this->preProcessHookObjects = array();
1627 $this->postProcessHookObjects = array();
1628 if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_db.php']['queryProcessors'])) {
1629 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_db.php']['queryProcessors'] as $classRef) {
1630 $hookObject = GeneralUtility::getUserObj($classRef);
1631 if (!(
1632 $hookObject instanceof PreProcessQueryHookInterface
1633 || $hookObject instanceof PostProcessQueryHookInterface
1634 )) {
1635 throw new \UnexpectedValueException(
1636 '$hookObject must either implement interface TYPO3\\CMS\\Core\\Database\\PreProcessQueryHookInterface or interface TYPO3\\CMS\\Core\\Database\\PostProcessQueryHookInterface',
1637 1299158548
1638 );
1639 }
1640 if ($hookObject instanceof PreProcessQueryHookInterface) {
1641 $this->preProcessHookObjects[] = $hookObject;
1642 }
1643 if ($hookObject instanceof PostProcessQueryHookInterface) {
1644 $this->postProcessHookObjects[] = $hookObject;
1645 }
1646 }
1647 }
1648 }
1649
1650 /**
1651 * Checks if database is connected
1652 *
1653 * @return bool
1654 */
1655 public function isConnected()
1656 {
1657 // We think we're still connected
1658 if ($this->isConnected) {
1659 // Check if this is really the case or if the database server has gone away for some reason
1660 // Using mysqlnd ping() does not reconnect (which we would not want anyway since charset etc would not be reinitialized that way)
1661 $this->isConnected = $this->link->ping();
1662 }
1663 return $this->isConnected;
1664 }
1665
1666 /**
1667 * Checks if the current connection character set has the same value
1668 * as the connectionCharset variable.
1669 *
1670 * To determine the character set these MySQL session variables are
1671 * checked: character_set_client, character_set_results and
1672 * character_set_connection.
1673 *
1674 * If the character set does not match or if the session variables
1675 * can not be read a RuntimeException is thrown.
1676 *
1677 * @return void
1678 * @throws \RuntimeException
1679 */
1680 protected function checkConnectionCharset()
1681 {
1682 $sessionResult = $this->sql_query('SHOW SESSION VARIABLES LIKE \'character_set%\'');
1683
1684 if ($sessionResult === false) {
1685 GeneralUtility::sysLog(
1686 'Error while retrieving the current charset session variables from the database: ' . $this->sql_error(),
1687 'core',
1688 GeneralUtility::SYSLOG_SEVERITY_ERROR
1689 );
1690 throw new \RuntimeException(
1691 'TYPO3 Fatal Error: Could not determine the current charset of the database.',
1692 1381847136
1693 );
1694 }
1695
1696 $charsetVariables = array();
1697 while (($row = $this->sql_fetch_row($sessionResult)) !== false) {
1698 $variableName = $row[0];
1699 $variableValue = $row[1];
1700 $charsetVariables[$variableName] = $variableValue;
1701 }
1702 $this->sql_free_result($sessionResult);
1703
1704 // These variables are set with the "Set names" command which was
1705 // used in the past. This is why we check them.
1706 $charsetRequiredVariables = array(
1707 'character_set_client',
1708 'character_set_results',
1709 'character_set_connection',
1710 );
1711
1712 $hasValidCharset = true;
1713 foreach ($charsetRequiredVariables as $variableName) {
1714 if (empty($charsetVariables[$variableName])) {
1715 GeneralUtility::sysLog(
1716 'A required session variable is missing in the current MySQL connection: ' . $variableName,
1717 'core',
1718 GeneralUtility::SYSLOG_SEVERITY_ERROR
1719 );
1720 throw new \RuntimeException(
1721 'TYPO3 Fatal Error: Could not determine the value of the database session variable: ' . $variableName,
1722 1381847779
1723 );
1724 }
1725
1726 if ($charsetVariables[$variableName] !== $this->connectionCharset) {
1727 $hasValidCharset = false;
1728 break;
1729 }
1730 }
1731
1732 if (!$hasValidCharset) {
1733 throw new \RuntimeException(
1734 'It looks like the character set ' . $this->connectionCharset . ' is not used for this connection even though it is configured as connection charset. ' .
1735 'This TYPO3 installation is using the $GLOBALS[\'TYPO3_CONF_VARS\'][\'SYS\'][\'setDBinit\'] property with the following value: "' .
1736 $GLOBALS['TYPO3_CONF_VARS']['SYS']['setDBinit'] . '". Please make sure that this command does not overwrite the configured charset. ' .
1737 'Please note that for the TYPO3 database everything other than utf8 is unsupported since version 4.7.',
1738 1389697515
1739 );
1740 }
1741 }
1742
1743 /**
1744 * Disconnect from database if connected
1745 *
1746 * @return void
1747 */
1748 protected function disconnectIfConnected()
1749 {
1750 if ($this->isConnected) {
1751 $this->link->close();
1752 $this->isConnected = false;
1753 }
1754 }
1755
1756 /**
1757 * Returns current database handle
1758 *
1759 * @return \mysqli|NULL
1760 */
1761 public function getDatabaseHandle()
1762 {
1763 return $this->link;
1764 }
1765
1766 /**
1767 * Set current database handle, usually \mysqli
1768 *
1769 * @param \mysqli $handle
1770 */
1771 public function setDatabaseHandle($handle)
1772 {
1773 $this->link = $handle;
1774 }
1775
1776 /**
1777 * Get the MySQL server version
1778 *
1779 * @return string
1780 */
1781 public function getServerVersion()
1782 {
1783 return $this->link->server_info;
1784 }
1785
1786 /******************************
1787 *
1788 * Debugging
1789 *
1790 ******************************/
1791 /**
1792 * Debug function: Outputs error if any
1793 *
1794 * @param string $func Function calling debug()
1795 * @param string $query Last query if not last built query
1796 * @return void
1797 */
1798 public function debug($func, $query = '')
1799 {
1800 $error = $this->sql_error();
1801 if ($error || (int)$this->debugOutput === 2) {
1802 \TYPO3\CMS\Core\Utility\DebugUtility::debug(
1803 array(
1804 'caller' => \TYPO3\CMS\Core\Database\DatabaseConnection::class . '::' . $func,
1805 'ERROR' => $error,
1806 'lastBuiltQuery' => $query ? $query : $this->debug_lastBuiltQuery,
1807 'debug_backtrace' => \TYPO3\CMS\Core\Utility\DebugUtility::debugTrail()
1808 ),
1809 $func,
1810 is_object($GLOBALS['error']) && @is_callable(array($GLOBALS['error'], 'debug'))
1811 ? ''
1812 : 'DB Error'
1813 );
1814 }
1815 }
1816
1817 /**
1818 * Checks if record set is valid and writes debugging information into devLog if not.
1819 *
1820 * @param bool|\mysqli_result|object MySQLi result object / DBAL object
1821 * @return bool TRUE if the record set is valid, FALSE otherwise
1822 */
1823 public function debug_check_recordset($res)
1824 {
1825 if ($res !== false) {
1826 return true;
1827 }
1828 $trace = debug_backtrace(0);
1829 array_shift($trace);
1830 $msg = 'Invalid database result detected: function TYPO3\\CMS\\Core\\Database\\DatabaseConnection->'
1831 . $trace[0]['function'] . ' called from file ' . substr($trace[0]['file'], (strlen(PATH_site) + 2))
1832 . ' in line ' . $trace[0]['line'] . '.';
1833 GeneralUtility::sysLog(
1834 $msg . ' Use a devLog extension to get more details.',
1835 'core',
1836 GeneralUtility::SYSLOG_SEVERITY_ERROR
1837 );
1838 // Send to devLog if enabled
1839 if (TYPO3_DLOG) {
1840 $debugLogData = array(
1841 'SQL Error' => $this->sql_error(),
1842 'Backtrace' => $trace
1843 );
1844 if ($this->debug_lastBuiltQuery) {
1845 $debugLogData = array('SQL Query' => $this->debug_lastBuiltQuery) + $debugLogData;
1846 }
1847 GeneralUtility::devLog($msg, 'Core/t3lib_db', 3, $debugLogData);
1848 }
1849 return false;
1850 }
1851
1852 /**
1853 * Explain select queries
1854 * If $this->explainOutput is set, SELECT queries will be explained here. Only queries with more than one possible result row will be displayed.
1855 * The output is either printed as raw HTML output or embedded into the TS admin panel (checkbox must be enabled!)
1856 *
1857 * @todo Feature is not DBAL-compliant
1858 *
1859 * @param string $query SQL query
1860 * @param string $from_table Table(s) from which to select. This is what comes right after "FROM ...". Required value.
1861 * @param int $row_count Number of resulting rows
1862 * @return bool TRUE if explain was run, FALSE otherwise
1863 */
1864 protected function explain($query, $from_table, $row_count)
1865 {
1866 $debugAllowedForIp = GeneralUtility::cmpIP(
1867 GeneralUtility::getIndpEnv('REMOTE_ADDR'),
1868 $GLOBALS['TYPO3_CONF_VARS']['SYS']['devIPmask']
1869 );
1870 if (
1871 (int)$this->explainOutput == 1
1872 || ((int)$this->explainOutput == 2 && $debugAllowedForIp)
1873 ) {
1874 // Raw HTML output
1875 $explainMode = 1;
1876 } elseif ((int)$this->explainOutput == 3) {
1877 // Embed the output into the TS admin panel
1878 $explainMode = 2;
1879 } else {
1880 return false;
1881 }
1882 $error = $this->sql_error();
1883 $trail = \TYPO3\CMS\Core\Utility\DebugUtility::debugTrail();
1884 $explain_tables = array();
1885 $explain_output = array();
1886 $res = $this->sql_query('EXPLAIN ' . $query, $this->link);
1887 if (is_a($res, '\\mysqli_result')) {
1888 while ($tempRow = $this->sql_fetch_assoc($res)) {
1889 $explain_output[] = $tempRow;
1890 $explain_tables[] = $tempRow['table'];
1891 }
1892 $this->sql_free_result($res);
1893 }
1894 $indices_output = array();
1895 // Notice: Rows are skipped if there is only one result, or if no conditions are set
1896 if ($explain_output[0]['rows'] > 1 || $explain_output[0]['type'] === 'ALL') {
1897 // Only enable output if it's really useful
1898 $debug = true;
1899 foreach ($explain_tables as $table) {
1900 $tableRes = $this->sql_query('SHOW TABLE STATUS LIKE \'' . $table . '\'');
1901 $isTable = $this->sql_num_rows($tableRes);
1902 if ($isTable) {
1903 $res = $this->sql_query('SHOW INDEX FROM ' . $table, $this->link);
1904 if (is_a($res, '\\mysqli_result')) {
1905 while ($tempRow = $this->sql_fetch_assoc($res)) {
1906 $indices_output[] = $tempRow;
1907 }
1908 $this->sql_free_result($res);
1909 }
1910 }
1911 $this->sql_free_result($tableRes);
1912 }
1913 } else {
1914 $debug = false;
1915 }
1916 if ($debug) {
1917 if ($explainMode) {
1918 $data = array();
1919 $data['query'] = $query;
1920 $data['trail'] = $trail;
1921 $data['row_count'] = $row_count;
1922 if ($error) {
1923 $data['error'] = $error;
1924 }
1925 if (!empty($explain_output)) {
1926 $data['explain'] = $explain_output;
1927 }
1928 if (!empty($indices_output)) {
1929 $data['indices'] = $indices_output;
1930 }
1931 if ($explainMode == 1) {
1932 \TYPO3\CMS\Core\Utility\DebugUtility::debug($data, 'Tables: ' . $from_table, 'DB SQL EXPLAIN');
1933 } elseif ($explainMode == 2) {
1934 /** @var TimeTracker $timeTracker */
1935 $timeTracker = GeneralUtility::makeInstance(TimeTracker::class);
1936 $timeTracker->setTSselectQuery($data);
1937 }
1938 }
1939 return true;
1940 }
1941 return false;
1942 }
1943
1944 /**
1945 * Serialize destructs current connection
1946 *
1947 * @return array All protected properties that should be saved
1948 */
1949 public function __sleep()
1950 {
1951 $this->disconnectIfConnected();
1952 return array(
1953 'debugOutput',
1954 'explainOutput',
1955 'databaseHost',
1956 'databasePort',
1957 'databaseSocket',
1958 'databaseName',
1959 'databaseUsername',
1960 'databaseUserPassword',
1961 'persistentDatabaseConnection',
1962 'connectionCompression',
1963 'initializeCommandsAfterConnect',
1964 'default_charset',
1965 );
1966 }
1967 }