Subversion-Projekte lars-tiefland.laravel_shop

Revision

Revision 621 | Details | Vergleich mit vorheriger | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 1
<?php
2
 
3
namespace Doctrine\DBAL;
4
 
5
use Closure;
6
use Doctrine\Common\EventManager;
7
use Doctrine\DBAL\Cache\ArrayResult;
8
use Doctrine\DBAL\Cache\CacheException;
9
use Doctrine\DBAL\Cache\QueryCacheProfile;
10
use Doctrine\DBAL\Driver\API\ExceptionConverter;
11
use Doctrine\DBAL\Driver\Connection as DriverConnection;
12
use Doctrine\DBAL\Driver\ServerInfoAwareConnection;
13
use Doctrine\DBAL\Driver\Statement as DriverStatement;
14
use Doctrine\DBAL\Event\TransactionBeginEventArgs;
15
use Doctrine\DBAL\Event\TransactionCommitEventArgs;
16
use Doctrine\DBAL\Event\TransactionRollBackEventArgs;
17
use Doctrine\DBAL\Exception\ConnectionLost;
18
use Doctrine\DBAL\Exception\DriverException;
19
use Doctrine\DBAL\Exception\InvalidArgumentException;
20
use Doctrine\DBAL\Platforms\AbstractPlatform;
21
use Doctrine\DBAL\Query\Expression\ExpressionBuilder;
22
use Doctrine\DBAL\Query\QueryBuilder;
23
use Doctrine\DBAL\Schema\AbstractSchemaManager;
621 lars 24
use Doctrine\DBAL\Schema\DefaultSchemaManagerFactory;
25
use Doctrine\DBAL\Schema\LegacySchemaManagerFactory;
26
use Doctrine\DBAL\Schema\SchemaManagerFactory;
148 lars 27
use Doctrine\DBAL\SQL\Parser;
28
use Doctrine\DBAL\Types\Type;
29
use Doctrine\Deprecations\Deprecation;
30
use LogicException;
621 lars 31
use SensitiveParameter;
148 lars 32
use Throwable;
33
use Traversable;
34
 
35
use function array_key_exists;
36
use function assert;
37
use function count;
38
use function get_class;
39
use function implode;
40
use function is_int;
41
use function is_string;
42
use function key;
43
use function method_exists;
44
use function sprintf;
45
 
46
/**
47
 * A database abstraction-level connection that implements features like events, transaction isolation levels,
48
 * configuration, emulated transaction nesting, lazy connecting and more.
49
 *
50
 * @psalm-import-type Params from DriverManager
51
 * @psalm-consistent-constructor
52
 */
53
class Connection
54
{
55
    /**
56
     * Represents an array of ints to be expanded by Doctrine SQL parsing.
621 lars 57
     *
58
     * @deprecated Use {@see ArrayParameterType::INTEGER} instead.
148 lars 59
     */
621 lars 60
    public const PARAM_INT_ARRAY = ArrayParameterType::INTEGER;
148 lars 61
 
62
    /**
63
     * Represents an array of strings to be expanded by Doctrine SQL parsing.
621 lars 64
     *
65
     * @deprecated Use {@see ArrayParameterType::STRING} instead.
148 lars 66
     */
621 lars 67
    public const PARAM_STR_ARRAY = ArrayParameterType::STRING;
148 lars 68
 
69
    /**
70
     * Represents an array of ascii strings to be expanded by Doctrine SQL parsing.
621 lars 71
     *
72
     * @deprecated Use {@see ArrayParameterType::ASCII} instead.
148 lars 73
     */
621 lars 74
    public const PARAM_ASCII_STR_ARRAY = ArrayParameterType::ASCII;
148 lars 75
 
76
    /**
77
     * Offset by which PARAM_* constants are detected as arrays of the param type.
78
     *
79
     * @internal Should be used only within the wrapper layer.
80
     */
81
    public const ARRAY_PARAM_OFFSET = 100;
82
 
83
    /**
84
     * The wrapped driver connection.
85
     *
621 lars 86
     * @var DriverConnection|null
148 lars 87
     */
88
    protected $_conn;
89
 
90
    /** @var Configuration */
91
    protected $_config;
92
 
93
    /**
94
     * @deprecated
95
     *
96
     * @var EventManager
97
     */
98
    protected $_eventManager;
99
 
100
    /**
101
     * @deprecated Use {@see createExpressionBuilder()} instead.
102
     *
103
     * @var ExpressionBuilder
104
     */
105
    protected $_expr;
106
 
107
    /**
108
     * The current auto-commit mode of this connection.
109
     */
110
    private bool $autoCommit = true;
111
 
112
    /**
113
     * The transaction nesting level.
114
     */
115
    private int $transactionNestingLevel = 0;
116
 
117
    /**
118
     * The currently active transaction isolation level or NULL before it has been determined.
119
     *
120
     * @var TransactionIsolationLevel::*|null
121
     */
122
    private $transactionIsolationLevel;
123
 
124
    /**
125
     * If nested transactions should use savepoints.
126
     */
127
    private bool $nestTransactionsWithSavepoints = false;
128
 
129
    /**
130
     * The parameters used during creation of the Connection instance.
131
     *
132
     * @var array<string,mixed>
133
     * @psalm-var Params
134
     */
135
    private array $params;
136
 
137
    /**
138
     * The database platform object used by the connection or NULL before it's initialized.
139
     */
140
    private ?AbstractPlatform $platform = null;
141
 
142
    private ?ExceptionConverter $exceptionConverter = null;
143
    private ?Parser $parser                         = null;
144
 
145
    /**
146
     * The schema manager.
147
     *
148
     * @deprecated Use {@see createSchemaManager()} instead.
149
     *
150
     * @var AbstractSchemaManager|null
151
     */
152
    protected $_schemaManager;
153
 
154
    /**
155
     * The used DBAL driver.
156
     *
157
     * @var Driver
158
     */
159
    protected $_driver;
160
 
161
    /**
162
     * Flag that indicates whether the current transaction is marked for rollback only.
163
     */
164
    private bool $isRollbackOnly = false;
165
 
621 lars 166
    private SchemaManagerFactory $schemaManagerFactory;
167
 
148 lars 168
    /**
169
     * Initializes a new instance of the Connection class.
170
     *
171
     * @internal The connection can be only instantiated by the driver manager.
172
     *
173
     * @param array<string,mixed> $params       The connection parameters.
174
     * @param Driver              $driver       The driver to use.
175
     * @param Configuration|null  $config       The configuration, optional.
176
     * @param EventManager|null   $eventManager The event manager, optional.
177
     * @psalm-param Params $params
178
     *
179
     * @throws Exception
180
     */
181
    public function __construct(
621 lars 182
        #[SensitiveParameter]
148 lars 183
        array $params,
184
        Driver $driver,
185
        ?Configuration $config = null,
186
        ?EventManager $eventManager = null
187
    ) {
188
        $this->_driver = $driver;
189
        $this->params  = $params;
190
 
191
        // Create default config and event manager if none given
192
        $config       ??= new Configuration();
193
        $eventManager ??= new EventManager();
194
 
195
        $this->_config       = $config;
196
        $this->_eventManager = $eventManager;
197
 
198
        if (isset($params['platform'])) {
199
            if (! $params['platform'] instanceof Platforms\AbstractPlatform) {
200
                throw Exception::invalidPlatformType($params['platform']);
201
            }
202
 
203
            Deprecation::trigger(
204
                'doctrine/dbal',
205
                'https://github.com/doctrine/dbal/pull/5699',
206
                'The "platform" connection parameter is deprecated.'
207
                    . ' Use a driver middleware that would instantiate the platform instead.',
208
            );
209
 
210
            $this->platform = $params['platform'];
211
            $this->platform->setEventManager($this->_eventManager);
212
        }
213
 
214
        $this->_expr = $this->createExpressionBuilder();
215
 
216
        $this->autoCommit = $config->getAutoCommit();
621 lars 217
 
218
        $schemaManagerFactory = $config->getSchemaManagerFactory();
219
        if ($schemaManagerFactory === null) {
220
            Deprecation::trigger(
221
                'doctrine/dbal',
222
                'https://github.com/doctrine/dbal/issues/5812',
223
                'Not configuring a schema manager factory is deprecated.'
224
                    . ' Use %s which is going to be the default in DBAL 4.',
225
                DefaultSchemaManagerFactory::class,
226
            );
227
 
228
            $schemaManagerFactory = new LegacySchemaManagerFactory();
229
        }
230
 
231
        $this->schemaManagerFactory = $schemaManagerFactory;
148 lars 232
    }
233
 
234
    /**
235
     * Gets the parameters used during instantiation.
236
     *
237
     * @internal
238
     *
239
     * @return array<string,mixed>
240
     * @psalm-return Params
241
     */
242
    public function getParams()
243
    {
244
        return $this->params;
245
    }
246
 
247
    /**
248
     * Gets the name of the currently selected database.
249
     *
250
     * @return string|null The name of the database or NULL if a database is not selected.
251
     *                     The platforms which don't support the concept of a database (e.g. embedded databases)
252
     *                     must always return a string as an indicator of an implicitly selected database.
253
     *
254
     * @throws Exception
255
     */
256
    public function getDatabase()
257
    {
258
        $platform = $this->getDatabasePlatform();
259
        $query    = $platform->getDummySelectSQL($platform->getCurrentDatabaseExpression());
260
        $database = $this->fetchOne($query);
261
 
262
        assert(is_string($database) || $database === null);
263
 
264
        return $database;
265
    }
266
 
267
    /**
268
     * Gets the DBAL driver instance.
269
     *
270
     * @return Driver
271
     */
272
    public function getDriver()
273
    {
274
        return $this->_driver;
275
    }
276
 
277
    /**
278
     * Gets the Configuration used by the Connection.
279
     *
280
     * @return Configuration
281
     */
282
    public function getConfiguration()
283
    {
284
        return $this->_config;
285
    }
286
 
287
    /**
288
     * Gets the EventManager used by the Connection.
289
     *
290
     * @deprecated
291
     *
292
     * @return EventManager
293
     */
294
    public function getEventManager()
295
    {
296
        Deprecation::triggerIfCalledFromOutside(
297
            'doctrine/dbal',
298
            'https://github.com/doctrine/dbal/issues/5784',
299
            '%s is deprecated.',
300
            __METHOD__,
301
        );
302
 
303
        return $this->_eventManager;
304
    }
305
 
306
    /**
307
     * Gets the DatabasePlatform for the connection.
308
     *
309
     * @return AbstractPlatform
310
     *
311
     * @throws Exception
312
     */
313
    public function getDatabasePlatform()
314
    {
315
        if ($this->platform === null) {
316
            $this->platform = $this->detectDatabasePlatform();
317
            $this->platform->setEventManager($this->_eventManager);
318
        }
319
 
320
        return $this->platform;
321
    }
322
 
323
    /**
324
     * Creates an expression builder for the connection.
325
     */
326
    public function createExpressionBuilder(): ExpressionBuilder
327
    {
328
        return new ExpressionBuilder($this);
329
    }
330
 
331
    /**
332
     * Gets the ExpressionBuilder for the connection.
333
     *
334
     * @deprecated Use {@see createExpressionBuilder()} instead.
335
     *
336
     * @return ExpressionBuilder
337
     */
338
    public function getExpressionBuilder()
339
    {
340
        Deprecation::triggerIfCalledFromOutside(
341
            'doctrine/dbal',
342
            'https://github.com/doctrine/dbal/issues/4515',
343
            'Connection::getExpressionBuilder() is deprecated,'
344
                . ' use Connection::createExpressionBuilder() instead.',
345
        );
346
 
347
        return $this->_expr;
348
    }
349
 
350
    /**
351
     * Establishes the connection with the database.
352
     *
353
     * @internal This method will be made protected in DBAL 4.0.
354
     *
355
     * @return bool TRUE if the connection was successfully established, FALSE if
356
     *              the connection is already open.
357
     *
358
     * @throws Exception
621 lars 359
     *
360
     * @psalm-assert !null $this->_conn
148 lars 361
     */
362
    public function connect()
363
    {
364
        Deprecation::triggerIfCalledFromOutside(
365
            'doctrine/dbal',
366
            'https://github.com/doctrine/dbal/issues/4966',
367
            'Public access to Connection::connect() is deprecated.',
368
        );
369
 
370
        if ($this->_conn !== null) {
371
            return false;
372
        }
373
 
374
        try {
375
            $this->_conn = $this->_driver->connect($this->params);
376
        } catch (Driver\Exception $e) {
377
            throw $this->convertException($e);
378
        }
379
 
380
        if ($this->autoCommit === false) {
381
            $this->beginTransaction();
382
        }
383
 
384
        if ($this->_eventManager->hasListeners(Events::postConnect)) {
385
            Deprecation::trigger(
386
                'doctrine/dbal',
387
                'https://github.com/doctrine/dbal/issues/5784',
388
                'Subscribing to %s events is deprecated. Implement a middleware instead.',
389
                Events::postConnect,
390
            );
391
 
392
            $eventArgs = new Event\ConnectionEventArgs($this);
393
            $this->_eventManager->dispatchEvent(Events::postConnect, $eventArgs);
394
        }
395
 
396
        return true;
397
    }
398
 
399
    /**
400
     * Detects and sets the database platform.
401
     *
402
     * Evaluates custom platform class and version in order to set the correct platform.
403
     *
404
     * @throws Exception If an invalid platform was specified for this connection.
405
     */
406
    private function detectDatabasePlatform(): AbstractPlatform
407
    {
408
        $version = $this->getDatabasePlatformVersion();
409
 
410
        if ($version !== null) {
411
            assert($this->_driver instanceof VersionAwarePlatformDriver);
412
 
413
            return $this->_driver->createDatabasePlatformForVersion($version);
414
        }
415
 
416
        return $this->_driver->getDatabasePlatform();
417
    }
418
 
419
    /**
420
     * Returns the version of the related platform if applicable.
421
     *
422
     * Returns null if either the driver is not capable to create version
423
     * specific platform instances, no explicit server version was specified
424
     * or the underlying driver connection cannot determine the platform
425
     * version without having to query it (performance reasons).
426
     *
427
     * @return string|null
428
     *
429
     * @throws Throwable
430
     */
431
    private function getDatabasePlatformVersion()
432
    {
433
        // Driver does not support version specific platforms.
434
        if (! $this->_driver instanceof VersionAwarePlatformDriver) {
435
            return null;
436
        }
437
 
438
        // Explicit platform version requested (supersedes auto-detection).
439
        if (isset($this->params['serverVersion'])) {
440
            return $this->params['serverVersion'];
441
        }
442
 
1464 lars 443
        if (isset($this->params['primary']) && isset($this->params['primary']['serverVersion'])) {
444
            return $this->params['primary']['serverVersion'];
445
        }
446
 
148 lars 447
        // If not connected, we need to connect now to determine the platform version.
448
        if ($this->_conn === null) {
449
            try {
450
                $this->connect();
451
            } catch (Exception $originalException) {
452
                if (! isset($this->params['dbname'])) {
453
                    throw $originalException;
454
                }
455
 
456
                Deprecation::trigger(
457
                    'doctrine/dbal',
458
                    'https://github.com/doctrine/dbal/pull/5707',
459
                    'Relying on a fallback connection used to determine the database platform while connecting'
460
                        . ' to a non-existing database is deprecated. Either use an existing database name in'
461
                        . ' connection parameters or omit the database name if the platform'
462
                        . ' and the server configuration allow that.',
463
                );
464
 
465
                // The database to connect to might not yet exist.
466
                // Retry detection without database name connection parameter.
467
                $params = $this->params;
468
 
469
                unset($this->params['dbname']);
470
 
471
                try {
472
                    $this->connect();
473
                } catch (Exception $fallbackException) {
474
                    // Either the platform does not support database-less connections
475
                    // or something else went wrong.
476
                    throw $originalException;
477
                } finally {
478
                    $this->params = $params;
479
                }
480
 
481
                $serverVersion = $this->getServerVersion();
482
 
483
                // Close "temporary" connection to allow connecting to the real database again.
484
                $this->close();
485
 
486
                return $serverVersion;
487
            }
488
        }
489
 
490
        return $this->getServerVersion();
491
    }
492
 
493
    /**
494
     * Returns the database server version if the underlying driver supports it.
495
     *
496
     * @return string|null
497
     *
498
     * @throws Exception
499
     */
500
    private function getServerVersion()
501
    {
502
        $connection = $this->getWrappedConnection();
503
 
504
        // Automatic platform version detection.
505
        if ($connection instanceof ServerInfoAwareConnection) {
506
            try {
507
                return $connection->getServerVersion();
508
            } catch (Driver\Exception $e) {
509
                throw $this->convertException($e);
510
            }
511
        }
512
 
513
        Deprecation::trigger(
514
            'doctrine/dbal',
515
            'https://github.com/doctrine/dbal/pull/4750',
516
            'Not implementing the ServerInfoAwareConnection interface in %s is deprecated',
517
            get_class($connection),
518
        );
519
 
520
        // Unable to detect platform version.
521
        return null;
522
    }
523
 
524
    /**
525
     * Returns the current auto-commit mode for this connection.
526
     *
527
     * @see    setAutoCommit
528
     *
529
     * @return bool True if auto-commit mode is currently enabled for this connection, false otherwise.
530
     */
531
    public function isAutoCommit()
532
    {
533
        return $this->autoCommit === true;
534
    }
535
 
536
    /**
537
     * Sets auto-commit mode for this connection.
538
     *
539
     * If a connection is in auto-commit mode, then all its SQL statements will be executed and committed as individual
540
     * transactions. Otherwise, its SQL statements are grouped into transactions that are terminated by a call to either
541
     * the method commit or the method rollback. By default, new connections are in auto-commit mode.
542
     *
543
     * NOTE: If this method is called during a transaction and the auto-commit mode is changed, the transaction is
544
     * committed. If this method is called and the auto-commit mode is not changed, the call is a no-op.
545
     *
546
     * @see   isAutoCommit
547
     *
548
     * @param bool $autoCommit True to enable auto-commit mode; false to disable it.
549
     *
550
     * @return void
551
     */
552
    public function setAutoCommit($autoCommit)
553
    {
554
        $autoCommit = (bool) $autoCommit;
555
 
556
        // Mode not changed, no-op.
557
        if ($autoCommit === $this->autoCommit) {
558
            return;
559
        }
560
 
561
        $this->autoCommit = $autoCommit;
562
 
563
        // Commit all currently active transactions if any when switching auto-commit mode.
564
        if ($this->_conn === null || $this->transactionNestingLevel === 0) {
565
            return;
566
        }
567
 
568
        $this->commitAll();
569
    }
570
 
571
    /**
572
     * Prepares and executes an SQL query and returns the first row of the result
573
     * as an associative array.
574
     *
575
     * @param string                                                               $query  SQL query
576
     * @param list<mixed>|array<string, mixed>                                     $params Query parameters
577
     * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types  Parameter types
578
     *
579
     * @return array<string, mixed>|false False is returned if no rows are found.
580
     *
581
     * @throws Exception
582
     */
583
    public function fetchAssociative(string $query, array $params = [], array $types = [])
584
    {
585
        return $this->executeQuery($query, $params, $types)->fetchAssociative();
586
    }
587
 
588
    /**
589
     * Prepares and executes an SQL query and returns the first row of the result
590
     * as a numerically indexed array.
591
     *
592
     * @param string                                                               $query  SQL query
593
     * @param list<mixed>|array<string, mixed>                                     $params Query parameters
594
     * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types  Parameter types
595
     *
596
     * @return list<mixed>|false False is returned if no rows are found.
597
     *
598
     * @throws Exception
599
     */
600
    public function fetchNumeric(string $query, array $params = [], array $types = [])
601
    {
602
        return $this->executeQuery($query, $params, $types)->fetchNumeric();
603
    }
604
 
605
    /**
606
     * Prepares and executes an SQL query and returns the value of a single column
607
     * of the first row of the result.
608
     *
609
     * @param string                                                               $query  SQL query
610
     * @param list<mixed>|array<string, mixed>                                     $params Query parameters
611
     * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types  Parameter types
612
     *
613
     * @return mixed|false False is returned if no rows are found.
614
     *
615
     * @throws Exception
616
     */
617
    public function fetchOne(string $query, array $params = [], array $types = [])
618
    {
619
        return $this->executeQuery($query, $params, $types)->fetchOne();
620
    }
621
 
622
    /**
623
     * Whether an actual connection to the database is established.
624
     *
625
     * @return bool
626
     */
627
    public function isConnected()
628
    {
629
        return $this->_conn !== null;
630
    }
631
 
632
    /**
633
     * Checks whether a transaction is currently active.
634
     *
635
     * @return bool TRUE if a transaction is currently active, FALSE otherwise.
636
     */
637
    public function isTransactionActive()
638
    {
639
        return $this->transactionNestingLevel > 0;
640
    }
641
 
642
    /**
643
     * Adds condition based on the criteria to the query components
644
     *
645
     * @param array<string,mixed> $criteria   Map of key columns to their values
646
     * @param string[]            $columns    Column names
647
     * @param mixed[]             $values     Column values
648
     * @param string[]            $conditions Key conditions
649
     *
650
     * @throws Exception
651
     */
652
    private function addCriteriaCondition(
653
        array $criteria,
654
        array &$columns,
655
        array &$values,
656
        array &$conditions
657
    ): void {
658
        $platform = $this->getDatabasePlatform();
659
 
660
        foreach ($criteria as $columnName => $value) {
661
            if ($value === null) {
662
                $conditions[] = $platform->getIsNullExpression($columnName);
663
                continue;
664
            }
665
 
666
            $columns[]    = $columnName;
667
            $values[]     = $value;
668
            $conditions[] = $columnName . ' = ?';
669
        }
670
    }
671
 
672
    /**
673
     * Executes an SQL DELETE statement on a table.
674
     *
675
     * Table expression and columns are not escaped and are not safe for user-input.
676
     *
677
     * @param string                                                               $table    Table name
678
     * @param array<string, mixed>                                                 $criteria Deletion criteria
679
     * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types    Parameter types
680
     *
681
     * @return int|string The number of affected rows.
682
     *
683
     * @throws Exception
684
     */
685
    public function delete($table, array $criteria, array $types = [])
686
    {
687
        if (count($criteria) === 0) {
688
            throw InvalidArgumentException::fromEmptyCriteria();
689
        }
690
 
691
        $columns = $values = $conditions = [];
692
 
693
        $this->addCriteriaCondition($criteria, $columns, $values, $conditions);
694
 
695
        return $this->executeStatement(
696
            'DELETE FROM ' . $table . ' WHERE ' . implode(' AND ', $conditions),
697
            $values,
698
            is_string(key($types)) ? $this->extractTypeValues($columns, $types) : $types,
699
        );
700
    }
701
 
702
    /**
703
     * Closes the connection.
704
     *
705
     * @return void
706
     */
707
    public function close()
708
    {
709
        $this->_conn                   = null;
710
        $this->transactionNestingLevel = 0;
711
    }
712
 
713
    /**
714
     * Sets the transaction isolation level.
715
     *
716
     * @param TransactionIsolationLevel::* $level The level to set.
717
     *
718
     * @return int|string
719
     *
720
     * @throws Exception
721
     */
722
    public function setTransactionIsolation($level)
723
    {
724
        $this->transactionIsolationLevel = $level;
725
 
726
        return $this->executeStatement($this->getDatabasePlatform()->getSetTransactionIsolationSQL($level));
727
    }
728
 
729
    /**
730
     * Gets the currently active transaction isolation level.
731
     *
732
     * @return TransactionIsolationLevel::* The current transaction isolation level.
733
     *
734
     * @throws Exception
735
     */
736
    public function getTransactionIsolation()
737
    {
738
        return $this->transactionIsolationLevel ??= $this->getDatabasePlatform()->getDefaultTransactionIsolationLevel();
739
    }
740
 
741
    /**
742
     * Executes an SQL UPDATE statement on a table.
743
     *
744
     * Table expression and columns are not escaped and are not safe for user-input.
745
     *
746
     * @param string                                                               $table    Table name
747
     * @param array<string, mixed>                                                 $data     Column-value pairs
748
     * @param array<string, mixed>                                                 $criteria Update criteria
749
     * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types    Parameter types
750
     *
751
     * @return int|string The number of affected rows.
752
     *
753
     * @throws Exception
754
     */
755
    public function update($table, array $data, array $criteria, array $types = [])
756
    {
757
        $columns = $values = $conditions = $set = [];
758
 
759
        foreach ($data as $columnName => $value) {
760
            $columns[] = $columnName;
761
            $values[]  = $value;
762
            $set[]     = $columnName . ' = ?';
763
        }
764
 
765
        $this->addCriteriaCondition($criteria, $columns, $values, $conditions);
766
 
767
        if (is_string(key($types))) {
768
            $types = $this->extractTypeValues($columns, $types);
769
        }
770
 
771
        $sql = 'UPDATE ' . $table . ' SET ' . implode(', ', $set)
772
                . ' WHERE ' . implode(' AND ', $conditions);
773
 
774
        return $this->executeStatement($sql, $values, $types);
775
    }
776
 
777
    /**
778
     * Inserts a table row with specified data.
779
     *
780
     * Table expression and columns are not escaped and are not safe for user-input.
781
     *
782
     * @param string                                                               $table Table name
783
     * @param array<string, mixed>                                                 $data  Column-value pairs
784
     * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types Parameter types
785
     *
786
     * @return int|string The number of affected rows.
787
     *
788
     * @throws Exception
789
     */
790
    public function insert($table, array $data, array $types = [])
791
    {
792
        if (count($data) === 0) {
793
            return $this->executeStatement('INSERT INTO ' . $table . ' () VALUES ()');
794
        }
795
 
796
        $columns = [];
797
        $values  = [];
798
        $set     = [];
799
 
800
        foreach ($data as $columnName => $value) {
801
            $columns[] = $columnName;
802
            $values[]  = $value;
803
            $set[]     = '?';
804
        }
805
 
806
        return $this->executeStatement(
807
            'INSERT INTO ' . $table . ' (' . implode(', ', $columns) . ')' .
808
            ' VALUES (' . implode(', ', $set) . ')',
809
            $values,
810
            is_string(key($types)) ? $this->extractTypeValues($columns, $types) : $types,
811
        );
812
    }
813
 
814
    /**
815
     * Extract ordered type list from an ordered column list and type map.
816
     *
817
     * @param array<int, string>                                                   $columnList
818
     * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types
819
     *
820
     * @return array<int, int|string|Type|null>|array<string, int|string|Type|null>
821
     */
822
    private function extractTypeValues(array $columnList, array $types): array
823
    {
824
        $typeValues = [];
825
 
826
        foreach ($columnList as $columnName) {
827
            $typeValues[] = $types[$columnName] ?? ParameterType::STRING;
828
        }
829
 
830
        return $typeValues;
831
    }
832
 
833
    /**
834
     * Quotes a string so it can be safely used as a table or column name, even if
835
     * it is a reserved name.
836
     *
837
     * Delimiting style depends on the underlying database platform that is being used.
838
     *
839
     * NOTE: Just because you CAN use quoted identifiers does not mean
840
     * you SHOULD use them. In general, they end up causing way more
841
     * problems than they solve.
842
     *
843
     * @param string $str The name to be quoted.
844
     *
845
     * @return string The quoted name.
846
     */
847
    public function quoteIdentifier($str)
848
    {
849
        return $this->getDatabasePlatform()->quoteIdentifier($str);
850
    }
851
 
852
    /**
853
     * The usage of this method is discouraged. Use prepared statements
854
     * or {@see AbstractPlatform::quoteStringLiteral()} instead.
855
     *
856
     * @param mixed                $value
857
     * @param int|string|Type|null $type
858
     *
859
     * @return mixed
860
     */
861
    public function quote($value, $type = ParameterType::STRING)
862
    {
863
        $connection = $this->getWrappedConnection();
864
 
865
        [$value, $bindingType] = $this->getBindingInfo($value, $type);
866
 
867
        return $connection->quote($value, $bindingType);
868
    }
869
 
870
    /**
871
     * Prepares and executes an SQL query and returns the result as an array of numeric arrays.
872
     *
873
     * @param string                                                               $query  SQL query
874
     * @param list<mixed>|array<string, mixed>                                     $params Query parameters
875
     * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types  Parameter types
876
     *
877
     * @return list<list<mixed>>
878
     *
879
     * @throws Exception
880
     */
881
    public function fetchAllNumeric(string $query, array $params = [], array $types = []): array
882
    {
883
        return $this->executeQuery($query, $params, $types)->fetchAllNumeric();
884
    }
885
 
886
    /**
887
     * Prepares and executes an SQL query and returns the result as an array of associative arrays.
888
     *
889
     * @param string                                                               $query  SQL query
890
     * @param list<mixed>|array<string, mixed>                                     $params Query parameters
891
     * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types  Parameter types
892
     *
893
     * @return list<array<string,mixed>>
894
     *
895
     * @throws Exception
896
     */
897
    public function fetchAllAssociative(string $query, array $params = [], array $types = []): array
898
    {
899
        return $this->executeQuery($query, $params, $types)->fetchAllAssociative();
900
    }
901
 
902
    /**
903
     * Prepares and executes an SQL query and returns the result as an associative array with the keys
904
     * mapped to the first column and the values mapped to the second column.
905
     *
906
     * @param string                                                               $query  SQL query
907
     * @param list<mixed>|array<string, mixed>                                     $params Query parameters
908
     * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types  Parameter types
909
     *
910
     * @return array<mixed,mixed>
911
     *
912
     * @throws Exception
913
     */
914
    public function fetchAllKeyValue(string $query, array $params = [], array $types = []): array
915
    {
916
        return $this->executeQuery($query, $params, $types)->fetchAllKeyValue();
917
    }
918
 
919
    /**
920
     * Prepares and executes an SQL query and returns the result as an associative array with the keys mapped
921
     * to the first column and the values being an associative array representing the rest of the columns
922
     * and their values.
923
     *
924
     * @param string                                                               $query  SQL query
925
     * @param list<mixed>|array<string, mixed>                                     $params Query parameters
926
     * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types  Parameter types
927
     *
928
     * @return array<mixed,array<string,mixed>>
929
     *
930
     * @throws Exception
931
     */
932
    public function fetchAllAssociativeIndexed(string $query, array $params = [], array $types = []): array
933
    {
934
        return $this->executeQuery($query, $params, $types)->fetchAllAssociativeIndexed();
935
    }
936
 
937
    /**
938
     * Prepares and executes an SQL query and returns the result as an array of the first column values.
939
     *
940
     * @param string                                                               $query  SQL query
941
     * @param list<mixed>|array<string, mixed>                                     $params Query parameters
942
     * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types  Parameter types
943
     *
944
     * @return list<mixed>
945
     *
946
     * @throws Exception
947
     */
948
    public function fetchFirstColumn(string $query, array $params = [], array $types = []): array
949
    {
950
        return $this->executeQuery($query, $params, $types)->fetchFirstColumn();
951
    }
952
 
953
    /**
954
     * Prepares and executes an SQL query and returns the result as an iterator over rows represented as numeric arrays.
955
     *
956
     * @param string                                                               $query  SQL query
957
     * @param list<mixed>|array<string, mixed>                                     $params Query parameters
958
     * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types  Parameter types
959
     *
960
     * @return Traversable<int,list<mixed>>
961
     *
962
     * @throws Exception
963
     */
964
    public function iterateNumeric(string $query, array $params = [], array $types = []): Traversable
965
    {
966
        return $this->executeQuery($query, $params, $types)->iterateNumeric();
967
    }
968
 
969
    /**
970
     * Prepares and executes an SQL query and returns the result as an iterator over rows represented
971
     * as associative arrays.
972
     *
973
     * @param string                                                               $query  SQL query
974
     * @param list<mixed>|array<string, mixed>                                     $params Query parameters
975
     * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types  Parameter types
976
     *
977
     * @return Traversable<int,array<string,mixed>>
978
     *
979
     * @throws Exception
980
     */
981
    public function iterateAssociative(string $query, array $params = [], array $types = []): Traversable
982
    {
983
        return $this->executeQuery($query, $params, $types)->iterateAssociative();
984
    }
985
 
986
    /**
987
     * Prepares and executes an SQL query and returns the result as an iterator with the keys
988
     * mapped to the first column and the values mapped to the second column.
989
     *
990
     * @param string                                                               $query  SQL query
991
     * @param list<mixed>|array<string, mixed>                                     $params Query parameters
992
     * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types  Parameter types
993
     *
994
     * @return Traversable<mixed,mixed>
995
     *
996
     * @throws Exception
997
     */
998
    public function iterateKeyValue(string $query, array $params = [], array $types = []): Traversable
999
    {
1000
        return $this->executeQuery($query, $params, $types)->iterateKeyValue();
1001
    }
1002
 
1003
    /**
1004
     * Prepares and executes an SQL query and returns the result as an iterator with the keys mapped
1005
     * to the first column and the values being an associative array representing the rest of the columns
1006
     * and their values.
1007
     *
1008
     * @param string                                           $query  SQL query
1009
     * @param list<mixed>|array<string, mixed>                 $params Query parameters
1010
     * @param array<int, int|string>|array<string, int|string> $types  Parameter types
1011
     *
1012
     * @return Traversable<mixed,array<string,mixed>>
1013
     *
1014
     * @throws Exception
1015
     */
1016
    public function iterateAssociativeIndexed(string $query, array $params = [], array $types = []): Traversable
1017
    {
1018
        return $this->executeQuery($query, $params, $types)->iterateAssociativeIndexed();
1019
    }
1020
 
1021
    /**
1022
     * Prepares and executes an SQL query and returns the result as an iterator over the first column values.
1023
     *
1024
     * @param string                                                               $query  SQL query
1025
     * @param list<mixed>|array<string, mixed>                                     $params Query parameters
1026
     * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types  Parameter types
1027
     *
1028
     * @return Traversable<int,mixed>
1029
     *
1030
     * @throws Exception
1031
     */
1032
    public function iterateColumn(string $query, array $params = [], array $types = []): Traversable
1033
    {
1034
        return $this->executeQuery($query, $params, $types)->iterateColumn();
1035
    }
1036
 
1037
    /**
1038
     * Prepares an SQL statement.
1039
     *
1040
     * @param string $sql The SQL statement to prepare.
1041
     *
1042
     * @throws Exception
1043
     */
1044
    public function prepare(string $sql): Statement
1045
    {
1046
        $connection = $this->getWrappedConnection();
1047
 
1048
        try {
1049
            $statement = $connection->prepare($sql);
1050
        } catch (Driver\Exception $e) {
1051
            throw $this->convertExceptionDuringQuery($e, $sql);
1052
        }
1053
 
1054
        return new Statement($this, $statement, $sql);
1055
    }
1056
 
1057
    /**
1464 lars 1058
     * Executes an, optionally parameterized, SQL query.
148 lars 1059
     *
1060
     * If the query is parametrized, a prepared statement is used.
1061
     * If an SQLLogger is configured, the execution is logged.
1062
     *
1063
     * @param string                                                               $sql    SQL query
1064
     * @param list<mixed>|array<string, mixed>                                     $params Query parameters
1065
     * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types  Parameter types
1066
     *
1067
     * @throws Exception
1068
     */
1069
    public function executeQuery(
1070
        string $sql,
1071
        array $params = [],
1072
        $types = [],
1073
        ?QueryCacheProfile $qcp = null
1074
    ): Result {
1075
        if ($qcp !== null) {
1076
            return $this->executeCacheQuery($sql, $params, $types, $qcp);
1077
        }
1078
 
1079
        $connection = $this->getWrappedConnection();
1080
 
1081
        $logger = $this->_config->getSQLLogger();
1082
        if ($logger !== null) {
1083
            $logger->startQuery($sql, $params, $types);
1084
        }
1085
 
1086
        try {
1087
            if (count($params) > 0) {
1088
                if ($this->needsArrayParameterConversion($params, $types)) {
1089
                    [$sql, $params, $types] = $this->expandArrayParameters($sql, $params, $types);
1090
                }
1091
 
1092
                $stmt = $connection->prepare($sql);
1093
 
1094
                $this->bindParameters($stmt, $params, $types);
1095
 
1096
                $result = $stmt->execute();
1097
            } else {
1098
                $result = $connection->query($sql);
1099
            }
1100
 
1101
            return new Result($result, $this);
1102
        } catch (Driver\Exception $e) {
1103
            throw $this->convertExceptionDuringQuery($e, $sql, $params, $types);
1104
        } finally {
1105
            if ($logger !== null) {
1106
                $logger->stopQuery();
1107
            }
1108
        }
1109
    }
1110
 
1111
    /**
1112
     * Executes a caching query.
1113
     *
1114
     * @param string                                                               $sql    SQL query
1115
     * @param list<mixed>|array<string, mixed>                                     $params Query parameters
1116
     * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types  Parameter types
1117
     *
1118
     * @throws CacheException
1119
     * @throws Exception
1120
     */
1121
    public function executeCacheQuery($sql, $params, $types, QueryCacheProfile $qcp): Result
1122
    {
1123
        $resultCache = $qcp->getResultCache() ?? $this->_config->getResultCache();
1124
 
1125
        if ($resultCache === null) {
1126
            throw CacheException::noResultDriverConfigured();
1127
        }
1128
 
1129
        $connectionParams = $this->params;
621 lars 1130
        unset($connectionParams['platform'], $connectionParams['password'], $connectionParams['url']);
148 lars 1131
 
1132
        [$cacheKey, $realKey] = $qcp->generateCacheKeys($sql, $params, $types, $connectionParams);
1133
 
1134
        $item = $resultCache->getItem($cacheKey);
1135
 
1136
        if ($item->isHit()) {
1137
            $value = $item->get();
1138
            if (isset($value[$realKey])) {
1139
                return new Result(new ArrayResult($value[$realKey]), $this);
1140
            }
1141
        } else {
1142
            $value = [];
1143
        }
1144
 
1145
        $data = $this->fetchAllAssociative($sql, $params, $types);
1146
 
1147
        $value[$realKey] = $data;
1148
 
1149
        $item->set($value);
1150
 
1151
        $lifetime = $qcp->getLifetime();
1152
        if ($lifetime > 0) {
1153
            $item->expiresAfter($lifetime);
1154
        }
1155
 
1156
        $resultCache->save($item);
1157
 
1158
        return new Result(new ArrayResult($data), $this);
1159
    }
1160
 
1161
    /**
1162
     * Executes an SQL statement with the given parameters and returns the number of affected rows.
1163
     *
1164
     * Could be used for:
1165
     *  - DML statements: INSERT, UPDATE, DELETE, etc.
1166
     *  - DDL statements: CREATE, DROP, ALTER, etc.
1167
     *  - DCL statements: GRANT, REVOKE, etc.
1168
     *  - Session control statements: ALTER SESSION, SET, DECLARE, etc.
1169
     *  - Other statements that don't yield a row set.
1170
     *
1171
     * This method supports PDO binding types as well as DBAL mapping types.
1172
     *
1173
     * @param string                                                               $sql    SQL statement
1174
     * @param list<mixed>|array<string, mixed>                                     $params Statement parameters
1175
     * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types  Parameter types
1176
     *
1177
     * @return int|string The number of affected rows.
1178
     *
1179
     * @throws Exception
1180
     */
1181
    public function executeStatement($sql, array $params = [], array $types = [])
1182
    {
1183
        $connection = $this->getWrappedConnection();
1184
 
1185
        $logger = $this->_config->getSQLLogger();
1186
        if ($logger !== null) {
1187
            $logger->startQuery($sql, $params, $types);
1188
        }
1189
 
1190
        try {
1191
            if (count($params) > 0) {
1192
                if ($this->needsArrayParameterConversion($params, $types)) {
1193
                    [$sql, $params, $types] = $this->expandArrayParameters($sql, $params, $types);
1194
                }
1195
 
1196
                $stmt = $connection->prepare($sql);
1197
 
1198
                $this->bindParameters($stmt, $params, $types);
1199
 
1200
                return $stmt->execute()
1201
                    ->rowCount();
1202
            }
1203
 
1204
            return $connection->exec($sql);
1205
        } catch (Driver\Exception $e) {
1206
            throw $this->convertExceptionDuringQuery($e, $sql, $params, $types);
1207
        } finally {
1208
            if ($logger !== null) {
1209
                $logger->stopQuery();
1210
            }
1211
        }
1212
    }
1213
 
1214
    /**
1215
     * Returns the current transaction nesting level.
1216
     *
1217
     * @return int The nesting level. A value of 0 means there's no active transaction.
1218
     */
1219
    public function getTransactionNestingLevel()
1220
    {
1221
        return $this->transactionNestingLevel;
1222
    }
1223
 
1224
    /**
1225
     * Returns the ID of the last inserted row, or the last value from a sequence object,
1226
     * depending on the underlying driver.
1227
     *
1228
     * Note: This method may not return a meaningful or consistent result across different drivers,
1229
     * because the underlying database may not even support the notion of AUTO_INCREMENT/IDENTITY
1230
     * columns or sequences.
1231
     *
1232
     * @param string|null $name Name of the sequence object from which the ID should be returned.
1233
     *
1234
     * @return string|int|false A string representation of the last inserted ID.
1235
     *
1236
     * @throws Exception
1237
     */
1238
    public function lastInsertId($name = null)
1239
    {
1240
        if ($name !== null) {
1241
            Deprecation::trigger(
1242
                'doctrine/dbal',
1243
                'https://github.com/doctrine/dbal/issues/4687',
1244
                'The usage of Connection::lastInsertId() with a sequence name is deprecated.',
1245
            );
1246
        }
1247
 
1248
        try {
1249
            return $this->getWrappedConnection()->lastInsertId($name);
1250
        } catch (Driver\Exception $e) {
1251
            throw $this->convertException($e);
1252
        }
1253
    }
1254
 
1255
    /**
1256
     * Executes a function in a transaction.
1257
     *
1258
     * The function gets passed this Connection instance as an (optional) parameter.
1259
     *
1260
     * If an exception occurs during execution of the function or transaction commit,
1261
     * the transaction is rolled back and the exception re-thrown.
1262
     *
1263
     * @param Closure(self):T $func The function to execute transactionally.
1264
     *
1265
     * @return T The value returned by $func
1266
     *
1267
     * @throws Throwable
1268
     *
1269
     * @template T
1270
     */
1271
    public function transactional(Closure $func)
1272
    {
1273
        $this->beginTransaction();
1274
        try {
1275
            $res = $func($this);
1276
            $this->commit();
1277
 
1278
            return $res;
1279
        } catch (Throwable $e) {
1280
            $this->rollBack();
1281
 
1282
            throw $e;
1283
        }
1284
    }
1285
 
1286
    /**
1287
     * Sets if nested transactions should use savepoints.
1288
     *
1289
     * @param bool $nestTransactionsWithSavepoints
1290
     *
1291
     * @return void
1292
     *
1293
     * @throws Exception
1294
     */
1295
    public function setNestTransactionsWithSavepoints($nestTransactionsWithSavepoints)
1296
    {
1297
        if (! $nestTransactionsWithSavepoints) {
1298
            Deprecation::trigger(
1299
                'doctrine/dbal',
1300
                'https://github.com/doctrine/dbal/pull/5383',
1301
                <<<'DEPRECATION'
1302
                Nesting transactions without enabling savepoints is deprecated.
1303
                Call %s::setNestTransactionsWithSavepoints(true) to enable savepoints.
1304
                DEPRECATION,
1305
                self::class,
1306
            );
1307
        }
1308
 
1309
        if ($this->transactionNestingLevel > 0) {
1310
            throw ConnectionException::mayNotAlterNestedTransactionWithSavepointsInTransaction();
1311
        }
1312
 
1313
        if (! $this->getDatabasePlatform()->supportsSavepoints()) {
1314
            throw ConnectionException::savepointsNotSupported();
1315
        }
1316
 
1317
        $this->nestTransactionsWithSavepoints = (bool) $nestTransactionsWithSavepoints;
1318
    }
1319
 
1320
    /**
1321
     * Gets if nested transactions should use savepoints.
1322
     *
1323
     * @return bool
1324
     */
1325
    public function getNestTransactionsWithSavepoints()
1326
    {
1327
        return $this->nestTransactionsWithSavepoints;
1328
    }
1329
 
1330
    /**
1331
     * Returns the savepoint name to use for nested transactions.
1332
     *
1333
     * @return string
1334
     */
1335
    protected function _getNestedTransactionSavePointName()
1336
    {
1337
        return 'DOCTRINE2_SAVEPOINT_' . $this->transactionNestingLevel;
1338
    }
1339
 
1340
    /**
1341
     * @return bool
1342
     *
1343
     * @throws Exception
1344
     */
1345
    public function beginTransaction()
1346
    {
1347
        $connection = $this->getWrappedConnection();
1348
 
1349
        ++$this->transactionNestingLevel;
1350
 
1351
        $logger = $this->_config->getSQLLogger();
1352
 
1353
        if ($this->transactionNestingLevel === 1) {
1354
            if ($logger !== null) {
1355
                $logger->startQuery('"START TRANSACTION"');
1356
            }
1357
 
1358
            $connection->beginTransaction();
1359
 
1360
            if ($logger !== null) {
1361
                $logger->stopQuery();
1362
            }
1363
        } elseif ($this->nestTransactionsWithSavepoints) {
1364
            if ($logger !== null) {
1365
                $logger->startQuery('"SAVEPOINT"');
1366
            }
1367
 
1368
            $this->createSavepoint($this->_getNestedTransactionSavePointName());
1369
            if ($logger !== null) {
1370
                $logger->stopQuery();
1371
            }
1372
        } else {
1373
            Deprecation::trigger(
1374
                'doctrine/dbal',
1375
                'https://github.com/doctrine/dbal/pull/5383',
1376
                <<<'DEPRECATION'
1377
                Nesting transactions without enabling savepoints is deprecated.
1378
                Call %s::setNestTransactionsWithSavepoints(true) to enable savepoints.
1379
                DEPRECATION,
1380
                self::class,
1381
            );
1382
        }
1383
 
1384
        $eventManager = $this->getEventManager();
1385
 
1386
        if ($eventManager->hasListeners(Events::onTransactionBegin)) {
1387
            Deprecation::trigger(
1388
                'doctrine/dbal',
1389
                'https://github.com/doctrine/dbal/issues/5784',
1390
                'Subscribing to %s events is deprecated.',
1391
                Events::onTransactionBegin,
1392
            );
1393
 
1394
            $eventManager->dispatchEvent(Events::onTransactionBegin, new TransactionBeginEventArgs($this));
1395
        }
1396
 
1397
        return true;
1398
    }
1399
 
1400
    /**
1401
     * @return bool
1402
     *
1403
     * @throws Exception
1404
     */
1405
    public function commit()
1406
    {
1407
        if ($this->transactionNestingLevel === 0) {
1408
            throw ConnectionException::noActiveTransaction();
1409
        }
1410
 
1411
        if ($this->isRollbackOnly) {
1412
            throw ConnectionException::commitFailedRollbackOnly();
1413
        }
1414
 
1415
        $result = true;
1416
 
1417
        $connection = $this->getWrappedConnection();
1418
 
1419
        if ($this->transactionNestingLevel === 1) {
1420
            $result = $this->doCommit($connection);
1421
        } elseif ($this->nestTransactionsWithSavepoints) {
1422
            $this->releaseSavepoint($this->_getNestedTransactionSavePointName());
1423
        }
1424
 
1425
        --$this->transactionNestingLevel;
1426
 
1427
        $eventManager = $this->getEventManager();
1428
 
1429
        if ($eventManager->hasListeners(Events::onTransactionCommit)) {
1430
            Deprecation::trigger(
1431
                'doctrine/dbal',
1432
                'https://github.com/doctrine/dbal/issues/5784',
1433
                'Subscribing to %s events is deprecated.',
1434
                Events::onTransactionCommit,
1435
            );
1436
 
1437
            $eventManager->dispatchEvent(Events::onTransactionCommit, new TransactionCommitEventArgs($this));
1438
        }
1439
 
1440
        if ($this->autoCommit !== false || $this->transactionNestingLevel !== 0) {
1441
            return $result;
1442
        }
1443
 
1444
        $this->beginTransaction();
1445
 
1446
        return $result;
1447
    }
1448
 
1449
    /**
1450
     * @return bool
1451
     *
1452
     * @throws DriverException
1453
     */
1454
    private function doCommit(DriverConnection $connection)
1455
    {
1456
        $logger = $this->_config->getSQLLogger();
1457
 
1458
        if ($logger !== null) {
1459
            $logger->startQuery('"COMMIT"');
1460
        }
1461
 
1462
        $result = $connection->commit();
1463
 
1464
        if ($logger !== null) {
1465
            $logger->stopQuery();
1466
        }
1467
 
1468
        return $result;
1469
    }
1470
 
1471
    /**
1472
     * Commits all current nesting transactions.
1473
     *
1474
     * @throws Exception
1475
     */
1476
    private function commitAll(): void
1477
    {
1478
        while ($this->transactionNestingLevel !== 0) {
1479
            if ($this->autoCommit === false && $this->transactionNestingLevel === 1) {
1480
                // When in no auto-commit mode, the last nesting commit immediately starts a new transaction.
1481
                // Therefore we need to do the final commit here and then leave to avoid an infinite loop.
1482
                $this->commit();
1483
 
1484
                return;
1485
            }
1486
 
1487
            $this->commit();
1488
        }
1489
    }
1490
 
1491
    /**
1492
     * Cancels any database changes done during the current transaction.
1493
     *
1494
     * @return bool
1495
     *
1496
     * @throws Exception
1497
     */
1498
    public function rollBack()
1499
    {
1500
        if ($this->transactionNestingLevel === 0) {
1501
            throw ConnectionException::noActiveTransaction();
1502
        }
1503
 
1504
        $connection = $this->getWrappedConnection();
1505
 
1506
        $logger = $this->_config->getSQLLogger();
1507
 
1508
        if ($this->transactionNestingLevel === 1) {
1509
            if ($logger !== null) {
1510
                $logger->startQuery('"ROLLBACK"');
1511
            }
1512
 
1513
            $this->transactionNestingLevel = 0;
1514
            $connection->rollBack();
1515
            $this->isRollbackOnly = false;
1516
            if ($logger !== null) {
1517
                $logger->stopQuery();
1518
            }
1519
 
1520
            if ($this->autoCommit === false) {
1521
                $this->beginTransaction();
1522
            }
1523
        } elseif ($this->nestTransactionsWithSavepoints) {
1524
            if ($logger !== null) {
1525
                $logger->startQuery('"ROLLBACK TO SAVEPOINT"');
1526
            }
1527
 
1528
            $this->rollbackSavepoint($this->_getNestedTransactionSavePointName());
1529
            --$this->transactionNestingLevel;
1530
            if ($logger !== null) {
1531
                $logger->stopQuery();
1532
            }
1533
        } else {
1534
            $this->isRollbackOnly = true;
1535
            --$this->transactionNestingLevel;
1536
        }
1537
 
1538
        $eventManager = $this->getEventManager();
1539
 
1540
        if ($eventManager->hasListeners(Events::onTransactionRollBack)) {
1541
            Deprecation::trigger(
1542
                'doctrine/dbal',
1543
                'https://github.com/doctrine/dbal/issues/5784',
1544
                'Subscribing to %s events is deprecated.',
1545
                Events::onTransactionRollBack,
1546
            );
1547
 
1548
            $eventManager->dispatchEvent(Events::onTransactionRollBack, new TransactionRollBackEventArgs($this));
1549
        }
1550
 
1551
        return true;
1552
    }
1553
 
1554
    /**
1555
     * Creates a new savepoint.
1556
     *
1557
     * @param string $savepoint The name of the savepoint to create.
1558
     *
1559
     * @return void
1560
     *
1561
     * @throws Exception
1562
     */
1563
    public function createSavepoint($savepoint)
1564
    {
1565
        $platform = $this->getDatabasePlatform();
1566
 
1567
        if (! $platform->supportsSavepoints()) {
1568
            throw ConnectionException::savepointsNotSupported();
1569
        }
1570
 
1571
        $this->executeStatement($platform->createSavePoint($savepoint));
1572
    }
1573
 
1574
    /**
1575
     * Releases the given savepoint.
1576
     *
1577
     * @param string $savepoint The name of the savepoint to release.
1578
     *
1579
     * @return void
1580
     *
1581
     * @throws Exception
1582
     */
1583
    public function releaseSavepoint($savepoint)
1584
    {
1585
        $logger = $this->_config->getSQLLogger();
1586
 
1587
        $platform = $this->getDatabasePlatform();
1588
 
1589
        if (! $platform->supportsSavepoints()) {
1590
            throw ConnectionException::savepointsNotSupported();
1591
        }
1592
 
1593
        if (! $platform->supportsReleaseSavepoints()) {
1594
            if ($logger !== null) {
1595
                $logger->stopQuery();
1596
            }
1597
 
1598
            return;
1599
        }
1600
 
1601
        if ($logger !== null) {
1602
            $logger->startQuery('"RELEASE SAVEPOINT"');
1603
        }
1604
 
1605
        $this->executeStatement($platform->releaseSavePoint($savepoint));
1606
 
1607
        if ($logger === null) {
1608
            return;
1609
        }
1610
 
1611
        $logger->stopQuery();
1612
    }
1613
 
1614
    /**
1615
     * Rolls back to the given savepoint.
1616
     *
1617
     * @param string $savepoint The name of the savepoint to rollback to.
1618
     *
1619
     * @return void
1620
     *
1621
     * @throws Exception
1622
     */
1623
    public function rollbackSavepoint($savepoint)
1624
    {
1625
        $platform = $this->getDatabasePlatform();
1626
 
1627
        if (! $platform->supportsSavepoints()) {
1628
            throw ConnectionException::savepointsNotSupported();
1629
        }
1630
 
1631
        $this->executeStatement($platform->rollbackSavePoint($savepoint));
1632
    }
1633
 
1634
    /**
1635
     * Gets the wrapped driver connection.
1636
     *
1637
     * @deprecated Use {@link getNativeConnection()} to access the native connection.
1638
     *
1639
     * @return DriverConnection
1640
     *
1641
     * @throws Exception
1642
     */
1643
    public function getWrappedConnection()
1644
    {
1645
        Deprecation::triggerIfCalledFromOutside(
1646
            'doctrine/dbal',
1647
            'https://github.com/doctrine/dbal/issues/4966',
1648
            'Connection::getWrappedConnection() is deprecated.'
1649
                . ' Use Connection::getNativeConnection() to access the native connection.',
1650
        );
1651
 
1652
        $this->connect();
1653
 
1654
        return $this->_conn;
1655
    }
1656
 
1657
    /** @return resource|object */
1658
    public function getNativeConnection()
1659
    {
1660
        $this->connect();
1661
 
1662
        if (! method_exists($this->_conn, 'getNativeConnection')) {
1663
            throw new LogicException(sprintf(
1664
                'The driver connection %s does not support accessing the native connection.',
1665
                get_class($this->_conn),
1666
            ));
1667
        }
1668
 
1669
        return $this->_conn->getNativeConnection();
1670
    }
1671
 
1672
    /**
1673
     * Creates a SchemaManager that can be used to inspect or change the
1674
     * database schema through the connection.
1675
     *
1676
     * @throws Exception
1677
     */
1678
    public function createSchemaManager(): AbstractSchemaManager
1679
    {
621 lars 1680
        return $this->schemaManagerFactory->createSchemaManager($this);
148 lars 1681
    }
1682
 
1683
    /**
1684
     * Gets the SchemaManager that can be used to inspect or change the
1685
     * database schema through the connection.
1686
     *
1687
     * @deprecated Use {@see createSchemaManager()} instead.
1688
     *
1689
     * @return AbstractSchemaManager
1690
     *
1691
     * @throws Exception
1692
     */
1693
    public function getSchemaManager()
1694
    {
1695
        Deprecation::triggerIfCalledFromOutside(
1696
            'doctrine/dbal',
1697
            'https://github.com/doctrine/dbal/issues/4515',
1698
            'Connection::getSchemaManager() is deprecated, use Connection::createSchemaManager() instead.',
1699
        );
1700
 
1701
        return $this->_schemaManager ??= $this->createSchemaManager();
1702
    }
1703
 
1704
    /**
1705
     * Marks the current transaction so that the only possible
1706
     * outcome for the transaction to be rolled back.
1707
     *
1708
     * @return void
1709
     *
1710
     * @throws ConnectionException If no transaction is active.
1711
     */
1712
    public function setRollbackOnly()
1713
    {
1714
        if ($this->transactionNestingLevel === 0) {
1715
            throw ConnectionException::noActiveTransaction();
1716
        }
1717
 
1718
        $this->isRollbackOnly = true;
1719
    }
1720
 
1721
    /**
1722
     * Checks whether the current transaction is marked for rollback only.
1723
     *
1724
     * @return bool
1725
     *
1726
     * @throws ConnectionException If no transaction is active.
1727
     */
1728
    public function isRollbackOnly()
1729
    {
1730
        if ($this->transactionNestingLevel === 0) {
1731
            throw ConnectionException::noActiveTransaction();
1732
        }
1733
 
1734
        return $this->isRollbackOnly;
1735
    }
1736
 
1737
    /**
1738
     * Converts a given value to its database representation according to the conversion
1739
     * rules of a specific DBAL mapping type.
1740
     *
1741
     * @param mixed  $value The value to convert.
1742
     * @param string $type  The name of the DBAL mapping type.
1743
     *
1744
     * @return mixed The converted value.
1745
     *
1746
     * @throws Exception
1747
     */
1748
    public function convertToDatabaseValue($value, $type)
1749
    {
1750
        return Type::getType($type)->convertToDatabaseValue($value, $this->getDatabasePlatform());
1751
    }
1752
 
1753
    /**
1754
     * Converts a given value to its PHP representation according to the conversion
1755
     * rules of a specific DBAL mapping type.
1756
     *
1757
     * @param mixed  $value The value to convert.
1758
     * @param string $type  The name of the DBAL mapping type.
1759
     *
1760
     * @return mixed The converted type.
1761
     *
1762
     * @throws Exception
1763
     */
1764
    public function convertToPHPValue($value, $type)
1765
    {
1766
        return Type::getType($type)->convertToPHPValue($value, $this->getDatabasePlatform());
1767
    }
1768
 
1769
    /**
1770
     * Binds a set of parameters, some or all of which are typed with a PDO binding type
1771
     * or DBAL mapping type, to a given statement.
1772
     *
1773
     * @param DriverStatement                                                      $stmt   Prepared statement
1774
     * @param list<mixed>|array<string, mixed>                                     $params Statement parameters
1775
     * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types  Parameter types
1776
     *
1777
     * @throws Exception
1778
     */
1779
    private function bindParameters(DriverStatement $stmt, array $params, array $types): void
1780
    {
1781
        // Check whether parameters are positional or named. Mixing is not allowed.
1782
        if (is_int(key($params))) {
1783
            $bindIndex = 1;
1784
 
1785
            foreach ($params as $key => $value) {
1786
                if (isset($types[$key])) {
1787
                    $type                  = $types[$key];
1788
                    [$value, $bindingType] = $this->getBindingInfo($value, $type);
1789
                } else {
1790
                    if (array_key_exists($key, $types)) {
1791
                        Deprecation::trigger(
1792
                            'doctrine/dbal',
1793
                            'https://github.com/doctrine/dbal/pull/5550',
1794
                            'Using NULL as prepared statement parameter type is deprecated.'
1795
                                . 'Omit or use Parameter::STRING instead',
1796
                        );
1797
                    }
1798
 
1799
                    $bindingType = ParameterType::STRING;
1800
                }
1801
 
1802
                $stmt->bindValue($bindIndex, $value, $bindingType);
1803
 
1804
                ++$bindIndex;
1805
            }
1806
        } else {
1807
            // Named parameters
1808
            foreach ($params as $name => $value) {
1809
                if (isset($types[$name])) {
1810
                    $type                  = $types[$name];
1811
                    [$value, $bindingType] = $this->getBindingInfo($value, $type);
1812
                } else {
1813
                    if (array_key_exists($name, $types)) {
1814
                        Deprecation::trigger(
1815
                            'doctrine/dbal',
1816
                            'https://github.com/doctrine/dbal/pull/5550',
1817
                            'Using NULL as prepared statement parameter type is deprecated.'
1818
                                . 'Omit or use Parameter::STRING instead',
1819
                        );
1820
                    }
1821
 
1822
                    $bindingType = ParameterType::STRING;
1823
                }
1824
 
1825
                $stmt->bindValue($name, $value, $bindingType);
1826
            }
1827
        }
1828
    }
1829
 
1830
    /**
1831
     * Gets the binding type of a given type.
1832
     *
1833
     * @param mixed                $value The value to bind.
1834
     * @param int|string|Type|null $type  The type to bind (PDO or DBAL).
1835
     *
1836
     * @return array{mixed, int} [0] => the (escaped) value, [1] => the binding type.
1837
     *
1838
     * @throws Exception
1839
     */
1840
    private function getBindingInfo($value, $type): array
1841
    {
1842
        if (is_string($type)) {
1843
            $type = Type::getType($type);
1844
        }
1845
 
1846
        if ($type instanceof Type) {
1847
            $value       = $type->convertToDatabaseValue($value, $this->getDatabasePlatform());
1848
            $bindingType = $type->getBindingType();
1849
        } else {
1850
            $bindingType = $type ?? ParameterType::STRING;
1851
        }
1852
 
1853
        return [$value, $bindingType];
1854
    }
1855
 
1856
    /**
1857
     * Creates a new instance of a SQL query builder.
1858
     *
1859
     * @return QueryBuilder
1860
     */
1861
    public function createQueryBuilder()
1862
    {
1863
        return new Query\QueryBuilder($this);
1864
    }
1865
 
1866
    /**
1867
     * @internal
1868
     *
1869
     * @param list<mixed>|array<string, mixed>                                     $params
1870
     * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types
1871
     */
1872
    final public function convertExceptionDuringQuery(
1873
        Driver\Exception $e,
1874
        string $sql,
1875
        array $params = [],
1876
        array $types = []
1877
    ): DriverException {
1878
        return $this->handleDriverException($e, new Query($sql, $params, $types));
1879
    }
1880
 
1881
    /** @internal */
1882
    final public function convertException(Driver\Exception $e): DriverException
1883
    {
1884
        return $this->handleDriverException($e, null);
1885
    }
1886
 
1887
    /**
1888
     * @param array<int, mixed>|array<string, mixed>                               $params
1889
     * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types
1890
     *
1891
     * @return array{string, list<mixed>, array<int,Type|int|string|null>}
1892
     */
1893
    private function expandArrayParameters(string $sql, array $params, array $types): array
1894
    {
1895
        $this->parser ??= $this->getDatabasePlatform()->createSQLParser();
1896
        $visitor        = new ExpandArrayParameters($params, $types);
1897
 
1898
        $this->parser->parse($sql, $visitor);
1899
 
1900
        return [
1901
            $visitor->getSQL(),
1902
            $visitor->getParameters(),
1903
            $visitor->getTypes(),
1904
        ];
1905
    }
1906
 
1907
    /**
1908
     * @param array<int, mixed>|array<string, mixed>                               $params
1909
     * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types
1910
     */
1911
    private function needsArrayParameterConversion(array $params, array $types): bool
1912
    {
1913
        if (is_string(key($params))) {
1914
            return true;
1915
        }
1916
 
1917
        foreach ($types as $type) {
1918
            if (
621 lars 1919
                $type === ArrayParameterType::INTEGER
1920
                || $type === ArrayParameterType::STRING
1921
                || $type === ArrayParameterType::ASCII
148 lars 1922
            ) {
1923
                return true;
1924
            }
1925
        }
1926
 
1927
        return false;
1928
    }
1929
 
1930
    private function handleDriverException(
1931
        Driver\Exception $driverException,
1932
        ?Query $query
1933
    ): DriverException {
1934
        $this->exceptionConverter ??= $this->_driver->getExceptionConverter();
1935
        $exception                  = $this->exceptionConverter->convert($driverException, $query);
1936
 
1937
        if ($exception instanceof ConnectionLost) {
1938
            $this->close();
1939
        }
1940
 
1941
        return $exception;
1942
    }
1943
 
1944
    /**
1945
     * BC layer for a wide-spread use-case of old DBAL APIs
1946
     *
1947
     * @deprecated This API is deprecated and will be removed after 2022
1948
     *
1949
     * @param array<mixed>           $params The query parameters
1950
     * @param array<int|string|null> $types  The parameter types
1951
     */
1952
    public function executeUpdate(string $sql, array $params = [], array $types = []): int
1953
    {
1954
        return $this->executeStatement($sql, $params, $types);
1955
    }
1956
 
1957
    /**
1958
     * BC layer for a wide-spread use-case of old DBAL APIs
1959
     *
1960
     * @deprecated This API is deprecated and will be removed after 2022
1961
     */
1962
    public function query(string $sql): Result
1963
    {
1964
        return $this->executeQuery($sql);
1965
    }
1966
 
1967
    /**
1968
     * BC layer for a wide-spread use-case of old DBAL APIs
1969
     *
1970
     * @deprecated This API is deprecated and will be removed after 2022
1971
     */
1972
    public function exec(string $sql): int
1973
    {
1974
        return $this->executeStatement($sql);
1975
    }
1976
}