[TASK] Upgrade typo3/phar-stream-wrapper to v3.1.3 (PHP 7.4)
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Database / Connection.php
1 <?php
2 declare(strict_types = 1);
3 namespace TYPO3\CMS\Core\Database;
4
5 /*
6 * This file is part of the TYPO3 CMS project.
7 *
8 * It is free software; you can redistribute it and/or modify it under
9 * the terms of the GNU General Public License, either version 2
10 * of the License, or any later version.
11 *
12 * For the full copyright and license information, please read the
13 * LICENSE.txt file that was distributed with this source code.
14 *
15 * The TYPO3 project - inspiring people to share!
16 */
17
18 use Doctrine\Common\EventManager;
19 use Doctrine\DBAL\Configuration;
20 use Doctrine\DBAL\Driver;
21 use Doctrine\DBAL\Driver\Statement;
22 use Doctrine\DBAL\Platforms\PostgreSqlPlatform;
23 use Doctrine\DBAL\Platforms\SQLServer2012Platform;
24 use Psr\Log\LoggerAwareInterface;
25 use Psr\Log\LoggerAwareTrait;
26 use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
27 use TYPO3\CMS\Core\Database\Query\QueryBuilder;
28 use TYPO3\CMS\Core\Utility\GeneralUtility;
29
30 class Connection extends \Doctrine\DBAL\Connection implements LoggerAwareInterface
31 {
32 use LoggerAwareTrait;
33
34 /**
35 * Represents a SQL NULL data type.
36 */
37 const PARAM_NULL = \PDO::PARAM_NULL; // 0
38
39 /**
40 * Represents a SQL INTEGER data type.
41 */
42 const PARAM_INT = \PDO::PARAM_INT; // 1
43
44 /**
45 * Represents a SQL CHAR, VARCHAR data type.
46 */
47 const PARAM_STR = \PDO::PARAM_STR; // 2
48
49 /**
50 * Represents a SQL large object data type.
51 */
52 const PARAM_LOB = \PDO::PARAM_LOB; // 3
53
54 /**
55 * Represents a recordset type. Not currently supported by any drivers.
56 */
57 const PARAM_STMT = \PDO::PARAM_STMT; // 4
58
59 /**
60 * Represents a boolean data type.
61 */
62 const PARAM_BOOL = \PDO::PARAM_BOOL; // 5
63
64 /**
65 * @var array
66 */
67 private $prepareConnectionCommands = [];
68
69 /**
70 * Initializes a new instance of the Connection class.
71 *
72 * @param array $params The connection parameters.
73 * @param Driver $driver The driver to use.
74 * @param Configuration|null $config The configuration, optional.
75 * @param EventManager|null $em The event manager, optional.
76 *
77 * @throws \Doctrine\DBAL\DBALException
78 */
79 public function __construct(array $params, Driver $driver, Configuration $config = null, EventManager $em = null)
80 {
81 parent::__construct($params, $driver, $config, $em);
82 $this->_expr = GeneralUtility::makeInstance(ExpressionBuilder::class, $this);
83 }
84
85 /**
86 * Gets the DatabasePlatform for the connection and initializes custom types and event listeners.
87 *
88 * @return bool
89 */
90 public function connect(): bool
91 {
92 // Early return if the connection is already open and custom setup has been done.
93 if (!parent::connect()) {
94 return false;
95 }
96
97 foreach ($this->prepareConnectionCommands as $command) {
98 if ($this->executeUpdate($command) === false) {
99 $this->logger->critical('Could not initialize DB connection with query "' . $command . '": ' . $this->errorInfo());
100 }
101 }
102
103 return true;
104 }
105
106 /**
107 * Creates a new instance of a SQL query builder.
108 *
109 * @return \TYPO3\CMS\Core\Database\Query\QueryBuilder
110 */
111 public function createQueryBuilder(): QueryBuilder
112 {
113 return GeneralUtility::makeInstance(QueryBuilder::class, $this);
114 }
115
116 /**
117 * Quotes a string so it can be safely used as a table or column name, even if
118 * it is a reserved name.
119 * EXAMPLE: tableName.fieldName => "tableName"."fieldName"
120 *
121 * Delimiting style depends on the underlying database platform that is being used.
122 *
123 * @param string $identifier The name to be quoted.
124 *
125 * @return string The quoted name.
126 */
127 public function quoteIdentifier($identifier): string
128 {
129 if ($identifier === '*') {
130 return $identifier;
131 }
132
133 return parent::quoteIdentifier($identifier);
134 }
135
136 /**
137 * Quotes an array of column names so it can be safely used, even if the name is a reserved name.
138 *
139 * Delimiting style depends on the underlying database platform that is being used.
140 *
141 * @param array $input
142 *
143 * @return array
144 */
145 public function quoteIdentifiers(array $input): array
146 {
147 return array_map([$this, 'quoteIdentifier'], $input);
148 }
149
150 /**
151 * Quotes an associative array of column-value so the column names can be safely used, even
152 * if the name is a reserved name.
153 *
154 * Delimiting style depends on the underlying database platform that is being used.
155 *
156 * @param array $input
157 *
158 * @return array
159 */
160 public function quoteColumnValuePairs(array $input): array
161 {
162 return array_combine($this->quoteIdentifiers(array_keys($input)), array_values($input));
163 }
164
165 /**
166 * Detect if the column types are specified by column name or using
167 * positional information. In the first case quote the field names
168 * accordingly.
169 *
170 * @param array $input
171 * @return array
172 */
173 protected function quoteColumnTypes(array $input): array
174 {
175 if (!is_string(key($input))) {
176 return $input;
177 }
178
179 return $this->quoteColumnValuePairs($input);
180 }
181
182 /**
183 * Inserts a table row with specified data.
184 *
185 * All SQL identifiers are expected to be unquoted and will be quoted when building the query.
186 * Table expression and columns are not escaped and are not safe for user-input.
187 *
188 * @param string $tableName The name of the table to insert data into.
189 * @param array $data An associative array containing column-value pairs.
190 * @param array $types Types of the inserted data.
191 *
192 * @return int The number of affected rows.
193 */
194 public function insert($tableName, array $data, array $types = []): int
195 {
196 return parent::insert(
197 $this->quoteIdentifier($tableName),
198 $this->quoteColumnValuePairs($data),
199 $this->quoteColumnTypes($types)
200 );
201 }
202
203 /**
204 * Bulk inserts table rows with specified data.
205 *
206 * All SQL identifiers are expected to be unquoted and will be quoted when building the query.
207 * Table expression and columns are not escaped and are not safe for user-input.
208 *
209 * @param string $tableName The name of the table to insert data into.
210 * @param array $data An array containing associative arrays of column-value pairs.
211 * @param array $columns An array containing associative arrays of column-value pairs.
212 * @param array $types Types of the inserted data.
213 *
214 * @return int The number of affected rows.
215 */
216 public function bulkInsert(string $tableName, array $data, array $columns = [], array $types = []): int
217 {
218 $query = GeneralUtility::makeInstance(Query\BulkInsertQuery::class, $this, $tableName, $columns);
219 foreach ($data as $values) {
220 $query->addValues($values, $types);
221 }
222
223 return $query->execute();
224 }
225
226 /**
227 * Executes an SQL SELECT statement on a table.
228 *
229 * All SQL identifiers are expected to be unquoted and will be quoted when building the query.
230 * Table expression and columns are not escaped and are not safe for user-input.
231 *
232 * @param string[] $columns The columns of the table which to select.
233 * @param string $tableName The name of the table on which to select.
234 * @param array $identifiers The selection criteria. An associative array containing column-value pairs.
235 * @param string[] $groupBy The columns to group the results by.
236 * @param array $orderBy Associative array of column name/sort directions pairs.
237 * @param int $limit The maximum number of rows to return.
238 * @param int $offset The first result row to select (when used with limit)
239 *
240 * @return Statement The executed statement.
241 */
242 public function select(
243 array $columns,
244 string $tableName,
245 array $identifiers = [],
246 array $groupBy = [],
247 array $orderBy = [],
248 int $limit = 0,
249 int $offset = 0
250 ): Statement {
251 $query = $this->createQueryBuilder();
252 $query->select(...$columns)
253 ->from($tableName);
254
255 foreach ($identifiers as $identifier => $value) {
256 $query->andWhere($query->expr()->eq($identifier, $query->createNamedParameter($value)));
257 }
258
259 foreach ($orderBy as $fieldName => $order) {
260 $query->addOrderBy($fieldName, $order);
261 }
262
263 if (!empty($groupBy)) {
264 $query->groupBy(...$groupBy);
265 }
266
267 if ($limit > 0) {
268 $query->setMaxResults($limit);
269 $query->setFirstResult($offset);
270 }
271
272 return $query->execute();
273 }
274
275 /**
276 * Executes an SQL UPDATE statement on a table.
277 *
278 * All SQL identifiers are expected to be unquoted and will be quoted when building the query.
279 * Table expression and columns are not escaped and are not safe for user-input.
280 *
281 * @param string $tableName The name of the table to update.
282 * @param array $data An associative array containing column-value pairs.
283 * @param array $identifier The update criteria. An associative array containing column-value pairs.
284 * @param array $types Types of the merged $data and $identifier arrays in that order.
285 *
286 * @return int The number of affected rows.
287 */
288 public function update($tableName, array $data, array $identifier, array $types = []): int
289 {
290 return parent::update(
291 $this->quoteIdentifier($tableName),
292 $this->quoteColumnValuePairs($data),
293 $this->quoteColumnValuePairs($identifier),
294 $this->quoteColumnTypes($types)
295 );
296 }
297
298 /**
299 * Executes an SQL DELETE statement on a table.
300 *
301 * All SQL identifiers are expected to be unquoted and will be quoted when building the query.
302 * Table expression and columns are not escaped and are not safe for user-input.
303 *
304 * @param string $tableName The name of the table on which to delete.
305 * @param array $identifier The deletion criteria. An associative array containing column-value pairs.
306 * @param array $types The types of identifiers.
307 *
308 * @return int The number of affected rows.
309 */
310 public function delete($tableName, array $identifier, array $types = []): int
311 {
312 return parent::delete(
313 $this->quoteIdentifier($tableName),
314 $this->quoteColumnValuePairs($identifier),
315 $this->quoteColumnTypes($types)
316 );
317 }
318
319 /**
320 * Executes an SQL TRUNCATE statement on a table.
321 *
322 * All SQL identifiers are expected to be unquoted and will be quoted when building the query.
323 * Table expression is not escaped and not safe for user-input.
324 *
325 * @param string $tableName The name of the table to truncate.
326 * @param bool $cascade Not supported on many platforms but would cascade the truncate by following foreign keys.
327 *
328 * @return int The number of affected rows. For a truncate this is unreliable as there is no meaningful information.
329 */
330 public function truncate(string $tableName, bool $cascade = false): int
331 {
332 return $this->executeUpdate(
333 $this->getDatabasePlatform()->getTruncateTableSQL(
334 $this->quoteIdentifier($tableName),
335 $cascade
336 )
337 );
338 }
339
340 /**
341 * Executes an SQL SELECT COUNT() statement on a table and returns the count result.
342 *
343 * @param string $item The column/expression of the table which to count
344 * @param string $tableName The name of the table on which to count.
345 * @param array $identifiers The selection criteria. An associative array containing column-value pairs.
346 *
347 * @return int The number of rows counted
348 */
349 public function count(string $item, string $tableName, array $identifiers): int
350 {
351 $query = $this->createQueryBuilder();
352 $query->count($item)
353 ->from($tableName);
354
355 foreach ($identifiers as $identifier => $value) {
356 $query->andWhere($query->expr()->eq($identifier, $query->createNamedParameter($value)));
357 }
358
359 return (int)$query->execute()->fetchColumn(0);
360 }
361
362 /**
363 * Returns the version of the current platform if applicable.
364 *
365 * If no version information is available only the platform name will be shown.
366 * If the platform name is unknown or unsupported the driver name will be shown.
367 *
368 * @return string
369 * @internal
370 */
371 public function getServerVersion(): string
372 {
373 $version = $this->getDatabasePlatform()->getName();
374 switch ($version) {
375 case 'mysql':
376 case 'pdo_mysql':
377 case 'drizzle_pdo_mysql':
378 $version = 'MySQL';
379 break;
380 case 'postgresql':
381 case 'pdo_postgresql':
382 $version = 'PostgreSQL';
383 break;
384 case 'oci8':
385 case 'pdo_oracle':
386 $version = 'Oracle';
387 break;
388 case 'sqlsrv':
389 case 'pdo_sqlsrv':
390 $version = 'SQLServer';
391 break;
392 }
393
394 // Driver does not support version specific platforms.
395 if (!$this->getDriver() instanceof \Doctrine\DBAL\VersionAwarePlatformDriver) {
396 return $version;
397 }
398
399 if ($this->getWrappedConnection() instanceof \Doctrine\DBAL\Driver\ServerInfoAwareConnection
400 && !$this->getWrappedConnection()->requiresQueryForServerVersion()
401 ) {
402 $version .= ' ' . $this->getWrappedConnection()->getServerVersion();
403 }
404
405 return $version;
406 }
407
408 /**
409 * Execute commands after initializing a new connection.
410 *
411 * @param string $commands
412 */
413 public function prepareConnection(string $commands)
414 {
415 if (empty($commands)) {
416 return;
417 }
418
419 $this->prepareConnectionCommands = GeneralUtility::trimExplode(
420 LF,
421 str_replace(
422 '\' . LF . \'',
423 LF,
424 $commands
425 ),
426 true
427 );
428 }
429
430 /**
431 * Returns the ID of the last inserted row or sequence value.
432 * If table and fieldname have been provided it tries to build
433 * the sequence name for PostgreSQL. For MySQL the parameters
434 * are not required / and only the table name is passed through.
435 *
436 * @param string|null $tableName
437 * @param string $fieldName
438 * @return string
439 */
440 public function lastInsertId($tableName = null, string $fieldName = 'uid'): string
441 {
442 $databasePlatform = $this->getDatabasePlatform();
443 if ($databasePlatform instanceof PostgreSqlPlatform) {
444 return parent::lastInsertId(trim(implode('_', [$tableName, $fieldName, 'seq']), '_'));
445 }
446 if ($databasePlatform instanceof SQLServer2012Platform) {
447 // lastInsertId() in mssql >2012 takes a sequence name and not the table name as
448 // argument. If no argument is given, last insert id of latest table is returned.
449 // https://docs.microsoft.com/de-de/sql/connect/php/pdo-lastinsertid?view=sql-server-2017
450 return (string)parent::lastInsertId();
451 }
452
453 return (string)parent::lastInsertId($tableName);
454 }
455 }