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