Passed
Push — master ( 373b25...9bb6b3 )
by
unknown
18:48
created

Connection::getExpressionBuilder()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 1
c 0
b 0
f 0
dl 0
loc 3
rs 10
cc 1
nc 1
nop 0
1
<?php
2
3
declare(strict_types=1);
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
namespace TYPO3\CMS\Core\Database;
19
20
use Doctrine\Common\EventManager;
21
use Doctrine\DBAL\Configuration;
22
use Doctrine\DBAL\Driver;
23
use Doctrine\DBAL\Driver\ServerInfoAwareConnection;
24
use Doctrine\DBAL\Driver\Statement;
25
use Doctrine\DBAL\ForwardCompatibility\Result;
0 ignored issues
show
Bug introduced by
The type Doctrine\DBAL\ForwardCompatibility\Result was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
26
use Doctrine\DBAL\Platforms\PostgreSQL94Platform as PostgreSqlPlatform;
27
use Doctrine\DBAL\Platforms\SQLServer2012Platform;
28
use Doctrine\DBAL\VersionAwarePlatformDriver;
29
use Psr\Log\LoggerAwareInterface;
30
use Psr\Log\LoggerAwareTrait;
31
use TYPO3\CMS\Core\Database\Query\BulkInsertQuery;
32
use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
33
use TYPO3\CMS\Core\Database\Query\QueryBuilder;
34
use TYPO3\CMS\Core\Utility\GeneralUtility;
35
36
class Connection extends \Doctrine\DBAL\Connection implements LoggerAwareInterface
37
{
38
    use LoggerAwareTrait;
39
40
    /**
41
     * Represents a SQL NULL data type.
42
     */
43
    const PARAM_NULL = \PDO::PARAM_NULL; // 0
44
45
    /**
46
     * Represents a SQL INTEGER data type.
47
     */
48
    const PARAM_INT = \PDO::PARAM_INT; // 1
49
50
    /**
51
     * Represents a SQL CHAR, VARCHAR data type.
52
     */
53
    const PARAM_STR = \PDO::PARAM_STR; // 2
54
55
    /**
56
     * Represents a SQL large object data type.
57
     */
58
    const PARAM_LOB = \PDO::PARAM_LOB; // 3
59
60
    /**
61
     * Represents a recordset type. Not currently supported by any drivers.
62
     */
63
    const PARAM_STMT = \PDO::PARAM_STMT; // 4
64
65
    /**
66
     * Represents a boolean data type.
67
     */
68
    const PARAM_BOOL = \PDO::PARAM_BOOL; // 5
69
70
    /** @var ExpressionBuilder */
71
    protected $_expr;
72
73
    /**
74
     * @var array
75
     */
76
    private $prepareConnectionCommands = [];
77
78
    /**
79
     * Initializes a new instance of the Connection class.
80
     *
81
     * @param array $params The connection parameters.
82
     * @param Driver $driver The driver to use.
83
     * @param Configuration|null $config The configuration, optional.
84
     * @param EventManager|null $em The event manager, optional.
85
     *
86
     * @throws \Doctrine\DBAL\Exception
87
     */
88
    public function __construct(array $params, Driver $driver, Configuration $config = null, EventManager $em = null)
89
    {
90
        parent::__construct($params, $driver, $config, $em);
91
        $this->_expr = GeneralUtility::makeInstance(ExpressionBuilder::class, $this);
92
    }
93
94
    /**
95
     * Gets the DatabasePlatform for the connection and initializes custom types and event listeners.
96
     *
97
     * @return bool
98
     */
99
    public function connect(): bool
100
    {
101
        // Early return if the connection is already open and custom setup has been done.
102
        if (!parent::connect()) {
103
            return false;
104
        }
105
106
        foreach ($this->prepareConnectionCommands as $command) {
107
            if ($this->executeUpdate($command) === false) {
108
                $this->logger->critical('Could not initialize DB connection with query "' . $command . '": ' . $this->errorInfo());
0 ignored issues
show
Bug introduced by
Are you sure $this->errorInfo() of type array<mixed,mixed> can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

108
                $this->logger->critical('Could not initialize DB connection with query "' . $command . '": ' . /** @scrutinizer ignore-type */ $this->errorInfo());
Loading history...
109
            }
110
        }
111
112
        return true;
113
    }
114
115
    /**
116
     * Creates a new instance of a SQL query builder.
117
     *
118
     * @return \TYPO3\CMS\Core\Database\Query\QueryBuilder
119
     */
120
    public function createQueryBuilder(): QueryBuilder
121
    {
122
        return GeneralUtility::makeInstance(QueryBuilder::class, $this);
123
    }
124
125
    /**
126
     * Quotes a string so it can be safely used as a table or column name, even if
127
     * it is a reserved name.
128
     * EXAMPLE: tableName.fieldName => "tableName"."fieldName"
129
     *
130
     * Delimiting style depends on the underlying database platform that is being used.
131
     *
132
     * @param string $identifier The name to be quoted.
133
     *
134
     * @return string The quoted name.
135
     */
136
    public function quoteIdentifier($identifier): string
137
    {
138
        if ($identifier === '*') {
139
            return $identifier;
140
        }
141
142
        return parent::quoteIdentifier($identifier);
143
    }
144
145
    /**
146
     * Quotes an array of column names so it can be safely used, even if the name is a reserved name.
147
     *
148
     * Delimiting style depends on the underlying database platform that is being used.
149
     *
150
     * @param array $input
151
     *
152
     * @return array
153
     */
154
    public function quoteIdentifiers(array $input): array
155
    {
156
        return array_map([$this, 'quoteIdentifier'], $input);
157
    }
158
159
    /**
160
     * Quotes an associative array of column-value so the column names can be safely used, even
161
     * if the name is a reserved name.
162
     *
163
     * Delimiting style depends on the underlying database platform that is being used.
164
     *
165
     * @param array $input
166
     *
167
     * @return array
168
     */
169
    public function quoteColumnValuePairs(array $input): array
170
    {
171
        return array_combine($this->quoteIdentifiers(array_keys($input)), array_values($input));
172
    }
173
174
    /**
175
     * Detect if the column types are specified by column name or using
176
     * positional information. In the first case quote the field names
177
     * accordingly.
178
     *
179
     * @param array $input
180
     * @return array
181
     */
182
    protected function quoteColumnTypes(array $input): array
183
    {
184
        if (!is_string(key($input))) {
185
            return $input;
186
        }
187
188
        return $this->quoteColumnValuePairs($input);
189
    }
190
191
    /**
192
     * Inserts a table row with specified data.
193
     *
194
     * All SQL identifiers are expected to be unquoted and will be quoted when building the query.
195
     * Table expression and columns are not escaped and are not safe for user-input.
196
     *
197
     * @param string $tableName The name of the table to insert data into.
198
     * @param array $data An associative array containing column-value pairs.
199
     * @param array $types Types of the inserted data.
200
     *
201
     * @return int The number of affected rows.
202
     */
203
    public function insert($tableName, array $data, array $types = []): int
204
    {
205
        return parent::insert(
206
            $this->quoteIdentifier($tableName),
207
            $this->quoteColumnValuePairs($data),
208
            $this->quoteColumnTypes($types)
209
        );
210
    }
211
212
    /**
213
     * Bulk inserts table rows with specified data.
214
     *
215
     * All SQL identifiers are expected to be unquoted and will be quoted when building the query.
216
     * Table expression and columns are not escaped and are not safe for user-input.
217
     *
218
     * @param string $tableName The name of the table to insert data into.
219
     * @param array $data An array containing associative arrays of column-value pairs or just the values to be inserted.
220
     * @param array $columns An array containing the column names of the data which should be inserted.
221
     * @param array $types Types of the inserted data.
222
     *
223
     * @return int The number of affected rows.
224
     */
225
    public function bulkInsert(string $tableName, array $data, array $columns = [], array $types = []): int
226
    {
227
        $query = GeneralUtility::makeInstance(BulkInsertQuery::class, $this, $tableName, $columns);
228
        foreach ($data as $values) {
229
            $query->addValues($values, $types);
230
        }
231
232
        return $query->execute();
233
    }
234
235
    /**
236
     * Executes an SQL SELECT statement on a table.
237
     *
238
     * All SQL identifiers are expected to be unquoted and will be quoted when building the query.
239
     * Table expression and columns are not escaped and are not safe for user-input.
240
     *
241
     * @param string[] $columns The columns of the table which to select.
242
     * @param string $tableName The name of the table on which to select.
243
     * @param array $identifiers The selection criteria. An associative array containing column-value pairs.
244
     * @param string[] $groupBy The columns to group the results by.
245
     * @param array $orderBy Associative array of column name/sort directions pairs.
246
     * @param int $limit The maximum number of rows to return.
247
     * @param int $offset The first result row to select (when used with limit)
248
     *
249
     * @return Statement|Result The executed statement.
250
     */
251
    public function select(
252
        array $columns,
253
        string $tableName,
254
        array $identifiers = [],
255
        array $groupBy = [],
256
        array $orderBy = [],
257
        int $limit = 0,
258
        int $offset = 0
259
    ) {
260
        $query = $this->createQueryBuilder();
261
        $query->select(...$columns)
262
            ->from($tableName);
263
264
        foreach ($identifiers as $identifier => $value) {
265
            $query->andWhere($query->expr()->eq($identifier, $query->createNamedParameter($value)));
266
        }
267
268
        foreach ($orderBy as $fieldName => $order) {
269
            $query->addOrderBy($fieldName, $order);
270
        }
271
272
        if (!empty($groupBy)) {
273
            $query->groupBy(...$groupBy);
274
        }
275
276
        if ($limit > 0) {
277
            $query->setMaxResults($limit);
278
            $query->setFirstResult($offset);
279
        }
280
281
        return $query->execute();
282
    }
283
284
    /**
285
     * Executes an SQL UPDATE statement on a table.
286
     *
287
     * All SQL identifiers are expected to be unquoted and will be quoted when building the query.
288
     * Table expression and columns are not escaped and are not safe for user-input.
289
     *
290
     * @param string $tableName The name of the table to update.
291
     * @param array $data An associative array containing column-value pairs.
292
     * @param array $identifier The update criteria. An associative array containing column-value pairs.
293
     * @param array $types Types of the merged $data and $identifier arrays in that order.
294
     *
295
     * @return int The number of affected rows.
296
     */
297
    public function update($tableName, array $data, array $identifier, array $types = []): int
298
    {
299
        return parent::update(
300
            $this->quoteIdentifier($tableName),
301
            $this->quoteColumnValuePairs($data),
302
            $this->quoteColumnValuePairs($identifier),
303
            $this->quoteColumnTypes($types)
304
        );
305
    }
306
307
    /**
308
     * Executes an SQL DELETE statement on a table.
309
     *
310
     * All SQL identifiers are expected to be unquoted and will be quoted when building the query.
311
     * Table expression and columns are not escaped and are not safe for user-input.
312
     *
313
     * @param string $tableName The name of the table on which to delete.
314
     * @param array $identifier The deletion criteria. An associative array containing column-value pairs.
315
     * @param array $types The types of identifiers.
316
     *
317
     * @return int The number of affected rows.
318
     */
319
    public function delete($tableName, array $identifier, array $types = []): int
320
    {
321
        return parent::delete(
322
            $this->quoteIdentifier($tableName),
323
            $this->quoteColumnValuePairs($identifier),
324
            $this->quoteColumnTypes($types)
325
        );
326
    }
327
328
    /**
329
     * Executes an SQL TRUNCATE statement on a table.
330
     *
331
     * All SQL identifiers are expected to be unquoted and will be quoted when building the query.
332
     * Table expression is not escaped and not safe for user-input.
333
     *
334
     * @param string $tableName The name of the table to truncate.
335
     * @param bool $cascade Not supported on many platforms but would cascade the truncate by following foreign keys.
336
     *
337
     * @return int The number of affected rows. For a truncate this is unreliable as there is no meaningful information.
338
     */
339
    public function truncate(string $tableName, bool $cascade = false): int
340
    {
341
        return $this->executeUpdate(
342
            $this->getDatabasePlatform()->getTruncateTableSQL(
343
                $this->quoteIdentifier($tableName),
344
                $cascade
345
            )
346
        );
347
    }
348
349
    /**
350
     * Executes an SQL SELECT COUNT() statement on a table and returns the count result.
351
     *
352
     * @param string $item The column/expression of the table which to count
353
     * @param string $tableName The name of the table on which to count.
354
     * @param array $identifiers The selection criteria. An associative array containing column-value pairs.
355
     *
356
     * @return int The number of rows counted
357
     */
358
    public function count(string $item, string $tableName, array $identifiers): int
359
    {
360
        $query = $this->createQueryBuilder();
361
        $query->count($item)
362
            ->from($tableName);
363
364
        foreach ($identifiers as $identifier => $value) {
365
            $query->andWhere($query->expr()->eq($identifier, $query->createNamedParameter($value)));
366
        }
367
368
        return (int)$query->execute()->fetchColumn(0);
369
    }
370
371
    /**
372
     * Returns the version of the current platform if applicable.
373
     *
374
     * If no version information is available only the platform name will be shown.
375
     * If the platform name is unknown or unsupported the driver name will be shown.
376
     *
377
     * @return string
378
     * @internal
379
     */
380
    public function getServerVersion(): string
381
    {
382
        $version = $this->getDatabasePlatform()->getName();
383
        switch ($version) {
384
            case 'mysql':
385
            case 'pdo_mysql':
386
            case 'drizzle_pdo_mysql':
387
                $version = 'MySQL';
388
                break;
389
            case 'postgresql':
390
            case 'pdo_postgresql':
391
                $version = 'PostgreSQL';
392
                break;
393
            case 'oci8':
394
            case 'pdo_oracle':
395
                $version = 'Oracle';
396
                break;
397
            case 'sqlsrv':
398
            case 'pdo_sqlsrv':
399
                $version = 'SQLServer';
400
                break;
401
        }
402
403
        // Driver does not support version specific platforms.
404
        if (!$this->getDriver() instanceof VersionAwarePlatformDriver) {
405
            return $version;
406
        }
407
408
        if ($this->getWrappedConnection() instanceof ServerInfoAwareConnection
409
            && !$this->getWrappedConnection()->requiresQueryForServerVersion()
410
        ) {
411
            $version .= ' ' . $this->getWrappedConnection()->getServerVersion();
412
        }
413
414
        return $version;
415
    }
416
417
    /**
418
     * Execute commands after initializing a new connection.
419
     *
420
     * @param string $commands
421
     */
422
    public function prepareConnection(string $commands)
423
    {
424
        if (empty($commands)) {
425
            return;
426
        }
427
428
        $this->prepareConnectionCommands = GeneralUtility::trimExplode(
429
            LF,
430
            str_replace(
431
                '\' . LF . \'',
432
                LF,
433
                $commands
434
            ),
435
            true
436
        );
437
    }
438
439
    /**
440
     * Returns the ID of the last inserted row or sequence value.
441
     * If table and fieldname have been provided it tries to build
442
     * the sequence name for PostgreSQL. For MySQL the parameters
443
     * are not required / and only the table name is passed through.
444
     *
445
     * @param string|null $tableName
446
     * @param string $fieldName
447
     * @return string
448
     */
449
    public function lastInsertId($tableName = null, string $fieldName = 'uid'): string
450
    {
451
        $databasePlatform = $this->getDatabasePlatform();
452
        if ($databasePlatform instanceof PostgreSqlPlatform) {
453
            return parent::lastInsertId(trim(implode('_', [$tableName, $fieldName, 'seq']), '_'));
454
        }
455
        if ($databasePlatform instanceof SQLServer2012Platform) {
456
            // lastInsertId() in mssql >2012 takes a sequence name and not the table name as
457
            // argument. If no argument is given, last insert id of latest table is returned.
458
            // https://docs.microsoft.com/de-de/sql/connect/php/pdo-lastinsertid?view=sql-server-2017
459
            return (string)parent::lastInsertId();
460
        }
461
462
        return (string)parent::lastInsertId($tableName);
463
    }
464
465
    /**
466
     * Gets the ExpressionBuilder for the connection.
467
     *
468
     * @return ExpressionBuilder
469
     */
470
    public function getExpressionBuilder()
471
    {
472
        return $this->_expr;
473
    }
474
}
475