Subversion-Projekte lars-tiefland.laravel_shop

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 1
<?php
2
 
3
namespace Illuminate\Database;
4
 
5
use Carbon\CarbonInterval;
6
use Closure;
7
use DateTimeInterface;
8
use Doctrine\DBAL\Connection as DoctrineConnection;
9
use Doctrine\DBAL\Types\Type;
10
use Exception;
11
use Illuminate\Contracts\Events\Dispatcher;
12
use Illuminate\Database\Events\QueryExecuted;
13
use Illuminate\Database\Events\StatementPrepared;
14
use Illuminate\Database\Events\TransactionBeginning;
15
use Illuminate\Database\Events\TransactionCommitted;
16
use Illuminate\Database\Events\TransactionCommitting;
17
use Illuminate\Database\Events\TransactionRolledBack;
18
use Illuminate\Database\Query\Builder as QueryBuilder;
19
use Illuminate\Database\Query\Expression;
20
use Illuminate\Database\Query\Grammars\Grammar as QueryGrammar;
21
use Illuminate\Database\Query\Processors\Processor;
22
use Illuminate\Database\Schema\Builder as SchemaBuilder;
23
use Illuminate\Support\Arr;
24
use Illuminate\Support\InteractsWithTime;
25
use Illuminate\Support\Traits\Macroable;
26
use PDO;
27
use PDOStatement;
28
use RuntimeException;
29
 
30
class Connection implements ConnectionInterface
31
{
32
    use DetectsConcurrencyErrors,
33
        DetectsLostConnections,
34
        Concerns\ManagesTransactions,
35
        InteractsWithTime,
36
        Macroable;
37
 
38
    /**
39
     * The active PDO connection.
40
     *
41
     * @var \PDO|\Closure
42
     */
43
    protected $pdo;
44
 
45
    /**
46
     * The active PDO connection used for reads.
47
     *
48
     * @var \PDO|\Closure
49
     */
50
    protected $readPdo;
51
 
52
    /**
53
     * The name of the connected database.
54
     *
55
     * @var string
56
     */
57
    protected $database;
58
 
59
    /**
60
     * The type of the connection.
61
     *
62
     * @var string|null
63
     */
64
    protected $readWriteType;
65
 
66
    /**
67
     * The table prefix for the connection.
68
     *
69
     * @var string
70
     */
71
    protected $tablePrefix = '';
72
 
73
    /**
74
     * The database connection configuration options.
75
     *
76
     * @var array
77
     */
78
    protected $config = [];
79
 
80
    /**
81
     * The reconnector instance for the connection.
82
     *
83
     * @var callable
84
     */
85
    protected $reconnector;
86
 
87
    /**
88
     * The query grammar implementation.
89
     *
90
     * @var \Illuminate\Database\Query\Grammars\Grammar
91
     */
92
    protected $queryGrammar;
93
 
94
    /**
95
     * The schema grammar implementation.
96
     *
97
     * @var \Illuminate\Database\Schema\Grammars\Grammar
98
     */
99
    protected $schemaGrammar;
100
 
101
    /**
102
     * The query post processor implementation.
103
     *
104
     * @var \Illuminate\Database\Query\Processors\Processor
105
     */
106
    protected $postProcessor;
107
 
108
    /**
109
     * The event dispatcher instance.
110
     *
111
     * @var \Illuminate\Contracts\Events\Dispatcher
112
     */
113
    protected $events;
114
 
115
    /**
116
     * The default fetch mode of the connection.
117
     *
118
     * @var int
119
     */
120
    protected $fetchMode = PDO::FETCH_OBJ;
121
 
122
    /**
123
     * The number of active transactions.
124
     *
125
     * @var int
126
     */
127
    protected $transactions = 0;
128
 
129
    /**
130
     * The transaction manager instance.
131
     *
132
     * @var \Illuminate\Database\DatabaseTransactionsManager
133
     */
134
    protected $transactionsManager;
135
 
136
    /**
137
     * Indicates if changes have been made to the database.
138
     *
139
     * @var bool
140
     */
141
    protected $recordsModified = false;
142
 
143
    /**
144
     * Indicates if the connection should use the "write" PDO connection.
145
     *
146
     * @var bool
147
     */
148
    protected $readOnWriteConnection = false;
149
 
150
    /**
151
     * All of the queries run against the connection.
152
     *
153
     * @var array
154
     */
155
    protected $queryLog = [];
156
 
157
    /**
158
     * Indicates whether queries are being logged.
159
     *
160
     * @var bool
161
     */
162
    protected $loggingQueries = false;
163
 
164
    /**
165
     * The duration of all executed queries in milliseconds.
166
     *
167
     * @var float
168
     */
169
    protected $totalQueryDuration = 0.0;
170
 
171
    /**
172
     * All of the registered query duration handlers.
173
     *
174
     * @var array
175
     */
176
    protected $queryDurationHandlers = [];
177
 
178
    /**
179
     * Indicates if the connection is in a "dry run".
180
     *
181
     * @var bool
182
     */
183
    protected $pretending = false;
184
 
185
    /**
186
     * All of the callbacks that should be invoked before a query is executed.
187
     *
188
     * @var \Closure[]
189
     */
190
    protected $beforeExecutingCallbacks = [];
191
 
192
    /**
193
     * The instance of Doctrine connection.
194
     *
195
     * @var \Doctrine\DBAL\Connection
196
     */
197
    protected $doctrineConnection;
198
 
199
    /**
200
     * Type mappings that should be registered with new Doctrine connections.
201
     *
202
     * @var array<string, string>
203
     */
204
    protected $doctrineTypeMappings = [];
205
 
206
    /**
207
     * The connection resolvers.
208
     *
209
     * @var \Closure[]
210
     */
211
    protected static $resolvers = [];
212
 
213
    /**
214
     * Create a new database connection instance.
215
     *
216
     * @param  \PDO|\Closure  $pdo
217
     * @param  string  $database
218
     * @param  string  $tablePrefix
219
     * @param  array  $config
220
     * @return void
221
     */
222
    public function __construct($pdo, $database = '', $tablePrefix = '', array $config = [])
223
    {
224
        $this->pdo = $pdo;
225
 
226
        // First we will setup the default properties. We keep track of the DB
227
        // name we are connected to since it is needed when some reflective
228
        // type commands are run such as checking whether a table exists.
229
        $this->database = $database;
230
 
231
        $this->tablePrefix = $tablePrefix;
232
 
233
        $this->config = $config;
234
 
235
        // We need to initialize a query grammar and the query post processors
236
        // which are both very important parts of the database abstractions
237
        // so we initialize these to their default values while starting.
238
        $this->useDefaultQueryGrammar();
239
 
240
        $this->useDefaultPostProcessor();
241
    }
242
 
243
    /**
244
     * Set the query grammar to the default implementation.
245
     *
246
     * @return void
247
     */
248
    public function useDefaultQueryGrammar()
249
    {
250
        $this->queryGrammar = $this->getDefaultQueryGrammar();
251
    }
252
 
253
    /**
254
     * Get the default query grammar instance.
255
     *
256
     * @return \Illuminate\Database\Query\Grammars\Grammar
257
     */
258
    protected function getDefaultQueryGrammar()
259
    {
260
        return new QueryGrammar;
261
    }
262
 
263
    /**
264
     * Set the schema grammar to the default implementation.
265
     *
266
     * @return void
267
     */
268
    public function useDefaultSchemaGrammar()
269
    {
270
        $this->schemaGrammar = $this->getDefaultSchemaGrammar();
271
    }
272
 
273
    /**
274
     * Get the default schema grammar instance.
275
     *
276
     * @return \Illuminate\Database\Schema\Grammars\Grammar
277
     */
278
    protected function getDefaultSchemaGrammar()
279
    {
280
        //
281
    }
282
 
283
    /**
284
     * Set the query post processor to the default implementation.
285
     *
286
     * @return void
287
     */
288
    public function useDefaultPostProcessor()
289
    {
290
        $this->postProcessor = $this->getDefaultPostProcessor();
291
    }
292
 
293
    /**
294
     * Get the default post processor instance.
295
     *
296
     * @return \Illuminate\Database\Query\Processors\Processor
297
     */
298
    protected function getDefaultPostProcessor()
299
    {
300
        return new Processor;
301
    }
302
 
303
    /**
304
     * Get a schema builder instance for the connection.
305
     *
306
     * @return \Illuminate\Database\Schema\Builder
307
     */
308
    public function getSchemaBuilder()
309
    {
310
        if (is_null($this->schemaGrammar)) {
311
            $this->useDefaultSchemaGrammar();
312
        }
313
 
314
        return new SchemaBuilder($this);
315
    }
316
 
317
    /**
318
     * Begin a fluent query against a database table.
319
     *
320
     * @param  \Closure|\Illuminate\Database\Query\Builder|string  $table
321
     * @param  string|null  $as
322
     * @return \Illuminate\Database\Query\Builder
323
     */
324
    public function table($table, $as = null)
325
    {
326
        return $this->query()->from($table, $as);
327
    }
328
 
329
    /**
330
     * Get a new query builder instance.
331
     *
332
     * @return \Illuminate\Database\Query\Builder
333
     */
334
    public function query()
335
    {
336
        return new QueryBuilder(
337
            $this, $this->getQueryGrammar(), $this->getPostProcessor()
338
        );
339
    }
340
 
341
    /**
342
     * Run a select statement and return a single result.
343
     *
344
     * @param  string  $query
345
     * @param  array  $bindings
346
     * @param  bool  $useReadPdo
347
     * @return mixed
348
     */
349
    public function selectOne($query, $bindings = [], $useReadPdo = true)
350
    {
351
        $records = $this->select($query, $bindings, $useReadPdo);
352
 
353
        return array_shift($records);
354
    }
355
 
356
    /**
357
     * Run a select statement and return the first column of the first row.
358
     *
359
     * @param  string  $query
360
     * @param  array  $bindings
361
     * @param  bool  $useReadPdo
362
     * @return mixed
363
     *
364
     * @throws \Illuminate\Database\MultipleColumnsSelectedException
365
     */
366
    public function scalar($query, $bindings = [], $useReadPdo = true)
367
    {
368
        $record = $this->selectOne($query, $bindings, $useReadPdo);
369
 
370
        if (is_null($record)) {
371
            return null;
372
        }
373
 
374
        $record = (array) $record;
375
 
376
        if (count($record) > 1) {
377
            throw new MultipleColumnsSelectedException;
378
        }
379
 
380
        return reset($record);
381
    }
382
 
383
    /**
384
     * Run a select statement against the database.
385
     *
386
     * @param  string  $query
387
     * @param  array  $bindings
388
     * @return array
389
     */
390
    public function selectFromWriteConnection($query, $bindings = [])
391
    {
392
        return $this->select($query, $bindings, false);
393
    }
394
 
395
    /**
396
     * Run a select statement against the database.
397
     *
398
     * @param  string  $query
399
     * @param  array  $bindings
400
     * @param  bool  $useReadPdo
401
     * @return array
402
     */
403
    public function select($query, $bindings = [], $useReadPdo = true)
404
    {
405
        return $this->run($query, $bindings, function ($query, $bindings) use ($useReadPdo) {
406
            if ($this->pretending()) {
407
                return [];
408
            }
409
 
410
            // For select statements, we'll simply execute the query and return an array
411
            // of the database result set. Each element in the array will be a single
412
            // row from the database table, and will either be an array or objects.
413
            $statement = $this->prepared(
414
                $this->getPdoForSelect($useReadPdo)->prepare($query)
415
            );
416
 
417
            $this->bindValues($statement, $this->prepareBindings($bindings));
418
 
419
            $statement->execute();
420
 
421
            return $statement->fetchAll();
422
        });
423
    }
424
 
425
    /**
426
     * Run a select statement against the database and returns a generator.
427
     *
428
     * @param  string  $query
429
     * @param  array  $bindings
430
     * @param  bool  $useReadPdo
431
     * @return \Generator
432
     */
433
    public function cursor($query, $bindings = [], $useReadPdo = true)
434
    {
435
        $statement = $this->run($query, $bindings, function ($query, $bindings) use ($useReadPdo) {
436
            if ($this->pretending()) {
437
                return [];
438
            }
439
 
440
            // First we will create a statement for the query. Then, we will set the fetch
441
            // mode and prepare the bindings for the query. Once that's done we will be
442
            // ready to execute the query against the database and return the cursor.
443
            $statement = $this->prepared($this->getPdoForSelect($useReadPdo)
444
                              ->prepare($query));
445
 
446
            $this->bindValues(
447
                $statement, $this->prepareBindings($bindings)
448
            );
449
 
450
            // Next, we'll execute the query against the database and return the statement
451
            // so we can return the cursor. The cursor will use a PHP generator to give
452
            // back one row at a time without using a bunch of memory to render them.
453
            $statement->execute();
454
 
455
            return $statement;
456
        });
457
 
458
        while ($record = $statement->fetch()) {
459
            yield $record;
460
        }
461
    }
462
 
463
    /**
464
     * Configure the PDO prepared statement.
465
     *
466
     * @param  \PDOStatement  $statement
467
     * @return \PDOStatement
468
     */
469
    protected function prepared(PDOStatement $statement)
470
    {
471
        $statement->setFetchMode($this->fetchMode);
472
 
473
        $this->event(new StatementPrepared($this, $statement));
474
 
475
        return $statement;
476
    }
477
 
478
    /**
479
     * Get the PDO connection to use for a select query.
480
     *
481
     * @param  bool  $useReadPdo
482
     * @return \PDO
483
     */
484
    protected function getPdoForSelect($useReadPdo = true)
485
    {
486
        return $useReadPdo ? $this->getReadPdo() : $this->getPdo();
487
    }
488
 
489
    /**
490
     * Run an insert statement against the database.
491
     *
492
     * @param  string  $query
493
     * @param  array  $bindings
494
     * @return bool
495
     */
496
    public function insert($query, $bindings = [])
497
    {
498
        return $this->statement($query, $bindings);
499
    }
500
 
501
    /**
502
     * Run an update statement against the database.
503
     *
504
     * @param  string  $query
505
     * @param  array  $bindings
506
     * @return int
507
     */
508
    public function update($query, $bindings = [])
509
    {
510
        return $this->affectingStatement($query, $bindings);
511
    }
512
 
513
    /**
514
     * Run a delete statement against the database.
515
     *
516
     * @param  string  $query
517
     * @param  array  $bindings
518
     * @return int
519
     */
520
    public function delete($query, $bindings = [])
521
    {
522
        return $this->affectingStatement($query, $bindings);
523
    }
524
 
525
    /**
526
     * Execute an SQL statement and return the boolean result.
527
     *
528
     * @param  string  $query
529
     * @param  array  $bindings
530
     * @return bool
531
     */
532
    public function statement($query, $bindings = [])
533
    {
534
        return $this->run($query, $bindings, function ($query, $bindings) {
535
            if ($this->pretending()) {
536
                return true;
537
            }
538
 
539
            $statement = $this->getPdo()->prepare($query);
540
 
541
            $this->bindValues($statement, $this->prepareBindings($bindings));
542
 
543
            $this->recordsHaveBeenModified();
544
 
545
            return $statement->execute();
546
        });
547
    }
548
 
549
    /**
550
     * Run an SQL statement and get the number of rows affected.
551
     *
552
     * @param  string  $query
553
     * @param  array  $bindings
554
     * @return int
555
     */
556
    public function affectingStatement($query, $bindings = [])
557
    {
558
        return $this->run($query, $bindings, function ($query, $bindings) {
559
            if ($this->pretending()) {
560
                return 0;
561
            }
562
 
563
            // For update or delete statements, we want to get the number of rows affected
564
            // by the statement and return that back to the developer. We'll first need
565
            // to execute the statement and then we'll use PDO to fetch the affected.
566
            $statement = $this->getPdo()->prepare($query);
567
 
568
            $this->bindValues($statement, $this->prepareBindings($bindings));
569
 
570
            $statement->execute();
571
 
572
            $this->recordsHaveBeenModified(
573
                ($count = $statement->rowCount()) > 0
574
            );
575
 
576
            return $count;
577
        });
578
    }
579
 
580
    /**
581
     * Run a raw, unprepared query against the PDO connection.
582
     *
583
     * @param  string  $query
584
     * @return bool
585
     */
586
    public function unprepared($query)
587
    {
588
        return $this->run($query, [], function ($query) {
589
            if ($this->pretending()) {
590
                return true;
591
            }
592
 
593
            $this->recordsHaveBeenModified(
594
                $change = $this->getPdo()->exec($query) !== false
595
            );
596
 
597
            return $change;
598
        });
599
    }
600
 
601
    /**
602
     * Execute the given callback in "dry run" mode.
603
     *
604
     * @param  \Closure  $callback
605
     * @return array
606
     */
607
    public function pretend(Closure $callback)
608
    {
609
        return $this->withFreshQueryLog(function () use ($callback) {
610
            $this->pretending = true;
611
 
612
            // Basically to make the database connection "pretend", we will just return
613
            // the default values for all the query methods, then we will return an
614
            // array of queries that were "executed" within the Closure callback.
615
            $callback($this);
616
 
617
            $this->pretending = false;
618
 
619
            return $this->queryLog;
620
        });
621
    }
622
 
623
    /**
624
     * Execute the given callback in "dry run" mode.
625
     *
626
     * @param  \Closure  $callback
627
     * @return array
628
     */
629
    protected function withFreshQueryLog($callback)
630
    {
631
        $loggingQueries = $this->loggingQueries;
632
 
633
        // First we will back up the value of the logging queries property and then
634
        // we'll be ready to run callbacks. This query log will also get cleared
635
        // so we will have a new log of all the queries that are executed now.
636
        $this->enableQueryLog();
637
 
638
        $this->queryLog = [];
639
 
640
        // Now we'll execute this callback and capture the result. Once it has been
641
        // executed we will restore the value of query logging and give back the
642
        // value of the callback so the original callers can have the results.
643
        $result = $callback();
644
 
645
        $this->loggingQueries = $loggingQueries;
646
 
647
        return $result;
648
    }
649
 
650
    /**
651
     * Bind values to their parameters in the given statement.
652
     *
653
     * @param  \PDOStatement  $statement
654
     * @param  array  $bindings
655
     * @return void
656
     */
657
    public function bindValues($statement, $bindings)
658
    {
659
        foreach ($bindings as $key => $value) {
660
            $statement->bindValue(
661
                is_string($key) ? $key : $key + 1,
662
                $value,
663
                match (true) {
664
                    is_int($value) => PDO::PARAM_INT,
665
                    is_resource($value) => PDO::PARAM_LOB,
666
                    default => PDO::PARAM_STR
667
                },
668
            );
669
        }
670
    }
671
 
672
    /**
673
     * Prepare the query bindings for execution.
674
     *
675
     * @param  array  $bindings
676
     * @return array
677
     */
678
    public function prepareBindings(array $bindings)
679
    {
680
        $grammar = $this->getQueryGrammar();
681
 
682
        foreach ($bindings as $key => $value) {
683
            // We need to transform all instances of DateTimeInterface into the actual
684
            // date string. Each query grammar maintains its own date string format
685
            // so we'll just ask the grammar for the format to get from the date.
686
            if ($value instanceof DateTimeInterface) {
687
                $bindings[$key] = $value->format($grammar->getDateFormat());
688
            } elseif (is_bool($value)) {
689
                $bindings[$key] = (int) $value;
690
            }
691
        }
692
 
693
        return $bindings;
694
    }
695
 
696
    /**
697
     * Run a SQL statement and log its execution context.
698
     *
699
     * @param  string  $query
700
     * @param  array  $bindings
701
     * @param  \Closure  $callback
702
     * @return mixed
703
     *
704
     * @throws \Illuminate\Database\QueryException
705
     */
706
    protected function run($query, $bindings, Closure $callback)
707
    {
708
        foreach ($this->beforeExecutingCallbacks as $beforeExecutingCallback) {
709
            $beforeExecutingCallback($query, $bindings, $this);
710
        }
711
 
712
        $this->reconnectIfMissingConnection();
713
 
714
        $start = microtime(true);
715
 
716
        // Here we will run this query. If an exception occurs we'll determine if it was
717
        // caused by a connection that has been lost. If that is the cause, we'll try
718
        // to re-establish connection and re-run the query with a fresh connection.
719
        try {
720
            $result = $this->runQueryCallback($query, $bindings, $callback);
721
        } catch (QueryException $e) {
722
            $result = $this->handleQueryException(
723
                $e, $query, $bindings, $callback
724
            );
725
        }
726
 
727
        // Once we have run the query we will calculate the time that it took to run and
728
        // then log the query, bindings, and execution time so we will report them on
729
        // the event that the developer needs them. We'll log time in milliseconds.
730
        $this->logQuery(
731
            $query, $bindings, $this->getElapsedTime($start)
732
        );
733
 
734
        return $result;
735
    }
736
 
737
    /**
738
     * Run a SQL statement.
739
     *
740
     * @param  string  $query
741
     * @param  array  $bindings
742
     * @param  \Closure  $callback
743
     * @return mixed
744
     *
745
     * @throws \Illuminate\Database\QueryException
746
     */
747
    protected function runQueryCallback($query, $bindings, Closure $callback)
748
    {
749
        // To execute the statement, we'll simply call the callback, which will actually
750
        // run the SQL against the PDO connection. Then we can calculate the time it
751
        // took to execute and log the query SQL, bindings and time in our memory.
752
        try {
753
            return $callback($query, $bindings);
754
        }
755
 
756
        // If an exception occurs when attempting to run a query, we'll format the error
757
        // message to include the bindings with SQL, which will make this exception a
758
        // lot more helpful to the developer instead of just the database's errors.
759
        catch (Exception $e) {
760
            throw new QueryException(
761
                $query, $this->prepareBindings($bindings), $e
762
            );
763
        }
764
    }
765
 
766
    /**
767
     * Log a query in the connection's query log.
768
     *
769
     * @param  string  $query
770
     * @param  array  $bindings
771
     * @param  float|null  $time
772
     * @return void
773
     */
774
    public function logQuery($query, $bindings, $time = null)
775
    {
776
        $this->totalQueryDuration += $time ?? 0.0;
777
 
778
        $this->event(new QueryExecuted($query, $bindings, $time, $this));
779
 
780
        if ($this->loggingQueries) {
781
            $this->queryLog[] = compact('query', 'bindings', 'time');
782
        }
783
    }
784
 
785
    /**
786
     * Get the elapsed time since a given starting point.
787
     *
788
     * @param  int  $start
789
     * @return float
790
     */
791
    protected function getElapsedTime($start)
792
    {
793
        return round((microtime(true) - $start) * 1000, 2);
794
    }
795
 
796
    /**
797
     * Register a callback to be invoked when the connection queries for longer than a given amount of time.
798
     *
799
     * @param  \DateTimeInterface|\Carbon\CarbonInterval|float|int  $threshold
800
     * @param  callable  $handler
801
     * @return void
802
     */
803
    public function whenQueryingForLongerThan($threshold, $handler)
804
    {
805
        $threshold = $threshold instanceof DateTimeInterface
806
            ? $this->secondsUntil($threshold) * 1000
807
            : $threshold;
808
 
809
        $threshold = $threshold instanceof CarbonInterval
810
            ? $threshold->totalMilliseconds
811
            : $threshold;
812
 
813
        $this->queryDurationHandlers[] = [
814
            'has_run' => false,
815
            'handler' => $handler,
816
        ];
817
 
818
        $key = count($this->queryDurationHandlers) - 1;
819
 
820
        $this->listen(function ($event) use ($threshold, $handler, $key) {
821
            if (! $this->queryDurationHandlers[$key]['has_run'] && $this->totalQueryDuration() > $threshold) {
822
                $handler($this, $event);
823
 
824
                $this->queryDurationHandlers[$key]['has_run'] = true;
825
            }
826
        });
827
    }
828
 
829
    /**
830
     * Allow all the query duration handlers to run again, even if they have already run.
831
     *
832
     * @return void
833
     */
834
    public function allowQueryDurationHandlersToRunAgain()
835
    {
836
        foreach ($this->queryDurationHandlers as $key => $queryDurationHandler) {
837
            $this->queryDurationHandlers[$key]['has_run'] = false;
838
        }
839
    }
840
 
841
    /**
842
     * Get the duration of all run queries in milliseconds.
843
     *
844
     * @return float
845
     */
846
    public function totalQueryDuration()
847
    {
848
        return $this->totalQueryDuration;
849
    }
850
 
851
    /**
852
     * Reset the duration of all run queries.
853
     *
854
     * @return void
855
     */
856
    public function resetTotalQueryDuration()
857
    {
858
        $this->totalQueryDuration = 0.0;
859
    }
860
 
861
    /**
862
     * Handle a query exception.
863
     *
864
     * @param  \Illuminate\Database\QueryException  $e
865
     * @param  string  $query
866
     * @param  array  $bindings
867
     * @param  \Closure  $callback
868
     * @return mixed
869
     *
870
     * @throws \Illuminate\Database\QueryException
871
     */
872
    protected function handleQueryException(QueryException $e, $query, $bindings, Closure $callback)
873
    {
874
        if ($this->transactions >= 1) {
875
            throw $e;
876
        }
877
 
878
        return $this->tryAgainIfCausedByLostConnection(
879
            $e, $query, $bindings, $callback
880
        );
881
    }
882
 
883
    /**
884
     * Handle a query exception that occurred during query execution.
885
     *
886
     * @param  \Illuminate\Database\QueryException  $e
887
     * @param  string  $query
888
     * @param  array  $bindings
889
     * @param  \Closure  $callback
890
     * @return mixed
891
     *
892
     * @throws \Illuminate\Database\QueryException
893
     */
894
    protected function tryAgainIfCausedByLostConnection(QueryException $e, $query, $bindings, Closure $callback)
895
    {
896
        if ($this->causedByLostConnection($e->getPrevious())) {
897
            $this->reconnect();
898
 
899
            return $this->runQueryCallback($query, $bindings, $callback);
900
        }
901
 
902
        throw $e;
903
    }
904
 
905
    /**
906
     * Reconnect to the database.
907
     *
908
     * @return mixed|false
909
     *
910
     * @throws \Illuminate\Database\LostConnectionException
911
     */
912
    public function reconnect()
913
    {
914
        if (is_callable($this->reconnector)) {
915
            $this->doctrineConnection = null;
916
 
917
            return call_user_func($this->reconnector, $this);
918
        }
919
 
920
        throw new LostConnectionException('Lost connection and no reconnector available.');
921
    }
922
 
923
    /**
924
     * Reconnect to the database if a PDO connection is missing.
925
     *
926
     * @return void
927
     */
928
    protected function reconnectIfMissingConnection()
929
    {
930
        if (is_null($this->pdo)) {
931
            $this->reconnect();
932
        }
933
    }
934
 
935
    /**
936
     * Disconnect from the underlying PDO connection.
937
     *
938
     * @return void
939
     */
940
    public function disconnect()
941
    {
942
        $this->setPdo(null)->setReadPdo(null);
943
 
944
        $this->doctrineConnection = null;
945
    }
946
 
947
    /**
948
     * Register a hook to be run just before a database query is executed.
949
     *
950
     * @param  \Closure  $callback
951
     * @return $this
952
     */
953
    public function beforeExecuting(Closure $callback)
954
    {
955
        $this->beforeExecutingCallbacks[] = $callback;
956
 
957
        return $this;
958
    }
959
 
960
    /**
961
     * Register a database query listener with the connection.
962
     *
963
     * @param  \Closure  $callback
964
     * @return void
965
     */
966
    public function listen(Closure $callback)
967
    {
968
        $this->events?->listen(Events\QueryExecuted::class, $callback);
969
    }
970
 
971
    /**
972
     * Fire an event for this connection.
973
     *
974
     * @param  string  $event
975
     * @return array|null
976
     */
977
    protected function fireConnectionEvent($event)
978
    {
979
        return $this->events?->dispatch(match ($event) {
980
            'beganTransaction' => new TransactionBeginning($this),
981
            'committed' => new TransactionCommitted($this),
982
            'committing' => new TransactionCommitting($this),
983
            'rollingBack' => new TransactionRolledBack($this),
984
            default => null,
985
        });
986
    }
987
 
988
    /**
989
     * Fire the given event if possible.
990
     *
991
     * @param  mixed  $event
992
     * @return void
993
     */
994
    protected function event($event)
995
    {
996
        $this->events?->dispatch($event);
997
    }
998
 
999
    /**
1000
     * Get a new raw query expression.
1001
     *
1002
     * @param  mixed  $value
1003
     * @return \Illuminate\Database\Query\Expression
1004
     */
1005
    public function raw($value)
1006
    {
1007
        return new Expression($value);
1008
    }
1009
 
1010
    /**
1011
     * Determine if the database connection has modified any database records.
1012
     *
1013
     * @return bool
1014
     */
1015
    public function hasModifiedRecords()
1016
    {
1017
        return $this->recordsModified;
1018
    }
1019
 
1020
    /**
1021
     * Indicate if any records have been modified.
1022
     *
1023
     * @param  bool  $value
1024
     * @return void
1025
     */
1026
    public function recordsHaveBeenModified($value = true)
1027
    {
1028
        if (! $this->recordsModified) {
1029
            $this->recordsModified = $value;
1030
        }
1031
    }
1032
 
1033
    /**
1034
     * Set the record modification state.
1035
     *
1036
     * @param  bool  $value
1037
     * @return $this
1038
     */
1039
    public function setRecordModificationState(bool $value)
1040
    {
1041
        $this->recordsModified = $value;
1042
 
1043
        return $this;
1044
    }
1045
 
1046
    /**
1047
     * Reset the record modification state.
1048
     *
1049
     * @return void
1050
     */
1051
    public function forgetRecordModificationState()
1052
    {
1053
        $this->recordsModified = false;
1054
    }
1055
 
1056
    /**
1057
     * Indicate that the connection should use the write PDO connection for reads.
1058
     *
1059
     * @param  bool  $value
1060
     * @return $this
1061
     */
1062
    public function useWriteConnectionWhenReading($value = true)
1063
    {
1064
        $this->readOnWriteConnection = $value;
1065
 
1066
        return $this;
1067
    }
1068
 
1069
    /**
1070
     * Is Doctrine available?
1071
     *
1072
     * @return bool
1073
     */
1074
    public function isDoctrineAvailable()
1075
    {
1076
        return class_exists('Doctrine\DBAL\Connection');
1077
    }
1078
 
1079
    /**
1080
     * Indicates whether native alter operations will be used when dropping or renaming columns, even if Doctrine DBAL is installed.
1081
     *
1082
     * @return bool
1083
     */
1084
    public function usingNativeSchemaOperations()
1085
    {
1086
        return ! $this->isDoctrineAvailable() || SchemaBuilder::$alwaysUsesNativeSchemaOperationsIfPossible;
1087
    }
1088
 
1089
    /**
1090
     * Get a Doctrine Schema Column instance.
1091
     *
1092
     * @param  string  $table
1093
     * @param  string  $column
1094
     * @return \Doctrine\DBAL\Schema\Column
1095
     */
1096
    public function getDoctrineColumn($table, $column)
1097
    {
1098
        $schema = $this->getDoctrineSchemaManager();
1099
 
1100
        return $schema->listTableDetails($table)->getColumn($column);
1101
    }
1102
 
1103
    /**
1104
     * Get the Doctrine DBAL schema manager for the connection.
1105
     *
1106
     * @return \Doctrine\DBAL\Schema\AbstractSchemaManager
1107
     */
1108
    public function getDoctrineSchemaManager()
1109
    {
1110
        $connection = $this->getDoctrineConnection();
1111
 
1112
        // Doctrine v2 expects one parameter while v3 expects two. 2nd will be ignored on v2...
1113
        return $this->getDoctrineDriver()->getSchemaManager(
1114
            $connection,
1115
            $connection->getDatabasePlatform()
1116
        );
1117
    }
1118
 
1119
    /**
1120
     * Get the Doctrine DBAL database connection instance.
1121
     *
1122
     * @return \Doctrine\DBAL\Connection
1123
     */
1124
    public function getDoctrineConnection()
1125
    {
1126
        if (is_null($this->doctrineConnection)) {
1127
            $driver = $this->getDoctrineDriver();
1128
 
1129
            $this->doctrineConnection = new DoctrineConnection(array_filter([
1130
                'pdo' => $this->getPdo(),
1131
                'dbname' => $this->getDatabaseName(),
1132
                'driver' => $driver->getName(),
1133
                'serverVersion' => $this->getConfig('server_version'),
1134
            ]), $driver);
1135
 
1136
            foreach ($this->doctrineTypeMappings as $name => $type) {
1137
                $this->doctrineConnection
1138
                    ->getDatabasePlatform()
1139
                    ->registerDoctrineTypeMapping($type, $name);
1140
            }
1141
        }
1142
 
1143
        return $this->doctrineConnection;
1144
    }
1145
 
1146
    /**
1147
     * Register a custom Doctrine mapping type.
1148
     *
1149
     * @param  Type|class-string<Type>  $class
1150
     * @param  string  $name
1151
     * @param  string  $type
1152
     * @return void
1153
     *
1154
     * @throws \Doctrine\DBAL\DBALException
1155
     * @throws \RuntimeException
1156
     */
1157
    public function registerDoctrineType(Type|string $class, string $name, string $type): void
1158
    {
1159
        if (! $this->isDoctrineAvailable()) {
1160
            throw new RuntimeException(
1161
                'Registering a custom Doctrine type requires Doctrine DBAL (doctrine/dbal).'
1162
            );
1163
        }
1164
 
1165
        if (! Type::hasType($name)) {
1166
            Type::getTypeRegistry()
1167
                ->register($name, is_string($class) ? new $class() : $class);
1168
        }
1169
 
1170
        $this->doctrineTypeMappings[$name] = $type;
1171
    }
1172
 
1173
    /**
1174
     * Get the current PDO connection.
1175
     *
1176
     * @return \PDO
1177
     */
1178
    public function getPdo()
1179
    {
1180
        if ($this->pdo instanceof Closure) {
1181
            return $this->pdo = call_user_func($this->pdo);
1182
        }
1183
 
1184
        return $this->pdo;
1185
    }
1186
 
1187
    /**
1188
     * Get the current PDO connection parameter without executing any reconnect logic.
1189
     *
1190
     * @return \PDO|\Closure|null
1191
     */
1192
    public function getRawPdo()
1193
    {
1194
        return $this->pdo;
1195
    }
1196
 
1197
    /**
1198
     * Get the current PDO connection used for reading.
1199
     *
1200
     * @return \PDO
1201
     */
1202
    public function getReadPdo()
1203
    {
1204
        if ($this->transactions > 0) {
1205
            return $this->getPdo();
1206
        }
1207
 
1208
        if ($this->readOnWriteConnection ||
1209
            ($this->recordsModified && $this->getConfig('sticky'))) {
1210
            return $this->getPdo();
1211
        }
1212
 
1213
        if ($this->readPdo instanceof Closure) {
1214
            return $this->readPdo = call_user_func($this->readPdo);
1215
        }
1216
 
1217
        return $this->readPdo ?: $this->getPdo();
1218
    }
1219
 
1220
    /**
1221
     * Get the current read PDO connection parameter without executing any reconnect logic.
1222
     *
1223
     * @return \PDO|\Closure|null
1224
     */
1225
    public function getRawReadPdo()
1226
    {
1227
        return $this->readPdo;
1228
    }
1229
 
1230
    /**
1231
     * Set the PDO connection.
1232
     *
1233
     * @param  \PDO|\Closure|null  $pdo
1234
     * @return $this
1235
     */
1236
    public function setPdo($pdo)
1237
    {
1238
        $this->transactions = 0;
1239
 
1240
        $this->pdo = $pdo;
1241
 
1242
        return $this;
1243
    }
1244
 
1245
    /**
1246
     * Set the PDO connection used for reading.
1247
     *
1248
     * @param  \PDO|\Closure|null  $pdo
1249
     * @return $this
1250
     */
1251
    public function setReadPdo($pdo)
1252
    {
1253
        $this->readPdo = $pdo;
1254
 
1255
        return $this;
1256
    }
1257
 
1258
    /**
1259
     * Set the reconnect instance on the connection.
1260
     *
1261
     * @param  callable  $reconnector
1262
     * @return $this
1263
     */
1264
    public function setReconnector(callable $reconnector)
1265
    {
1266
        $this->reconnector = $reconnector;
1267
 
1268
        return $this;
1269
    }
1270
 
1271
    /**
1272
     * Get the database connection name.
1273
     *
1274
     * @return string|null
1275
     */
1276
    public function getName()
1277
    {
1278
        return $this->getConfig('name');
1279
    }
1280
 
1281
    /**
1282
     * Get the database connection full name.
1283
     *
1284
     * @return string|null
1285
     */
1286
    public function getNameWithReadWriteType()
1287
    {
1288
        return $this->getName().($this->readWriteType ? '::'.$this->readWriteType : '');
1289
    }
1290
 
1291
    /**
1292
     * Get an option from the configuration options.
1293
     *
1294
     * @param  string|null  $option
1295
     * @return mixed
1296
     */
1297
    public function getConfig($option = null)
1298
    {
1299
        return Arr::get($this->config, $option);
1300
    }
1301
 
1302
    /**
1303
     * Get the PDO driver name.
1304
     *
1305
     * @return string
1306
     */
1307
    public function getDriverName()
1308
    {
1309
        return $this->getConfig('driver');
1310
    }
1311
 
1312
    /**
1313
     * Get the query grammar used by the connection.
1314
     *
1315
     * @return \Illuminate\Database\Query\Grammars\Grammar
1316
     */
1317
    public function getQueryGrammar()
1318
    {
1319
        return $this->queryGrammar;
1320
    }
1321
 
1322
    /**
1323
     * Set the query grammar used by the connection.
1324
     *
1325
     * @param  \Illuminate\Database\Query\Grammars\Grammar  $grammar
1326
     * @return $this
1327
     */
1328
    public function setQueryGrammar(Query\Grammars\Grammar $grammar)
1329
    {
1330
        $this->queryGrammar = $grammar;
1331
 
1332
        return $this;
1333
    }
1334
 
1335
    /**
1336
     * Get the schema grammar used by the connection.
1337
     *
1338
     * @return \Illuminate\Database\Schema\Grammars\Grammar
1339
     */
1340
    public function getSchemaGrammar()
1341
    {
1342
        return $this->schemaGrammar;
1343
    }
1344
 
1345
    /**
1346
     * Set the schema grammar used by the connection.
1347
     *
1348
     * @param  \Illuminate\Database\Schema\Grammars\Grammar  $grammar
1349
     * @return $this
1350
     */
1351
    public function setSchemaGrammar(Schema\Grammars\Grammar $grammar)
1352
    {
1353
        $this->schemaGrammar = $grammar;
1354
 
1355
        return $this;
1356
    }
1357
 
1358
    /**
1359
     * Get the query post processor used by the connection.
1360
     *
1361
     * @return \Illuminate\Database\Query\Processors\Processor
1362
     */
1363
    public function getPostProcessor()
1364
    {
1365
        return $this->postProcessor;
1366
    }
1367
 
1368
    /**
1369
     * Set the query post processor used by the connection.
1370
     *
1371
     * @param  \Illuminate\Database\Query\Processors\Processor  $processor
1372
     * @return $this
1373
     */
1374
    public function setPostProcessor(Processor $processor)
1375
    {
1376
        $this->postProcessor = $processor;
1377
 
1378
        return $this;
1379
    }
1380
 
1381
    /**
1382
     * Get the event dispatcher used by the connection.
1383
     *
1384
     * @return \Illuminate\Contracts\Events\Dispatcher
1385
     */
1386
    public function getEventDispatcher()
1387
    {
1388
        return $this->events;
1389
    }
1390
 
1391
    /**
1392
     * Set the event dispatcher instance on the connection.
1393
     *
1394
     * @param  \Illuminate\Contracts\Events\Dispatcher  $events
1395
     * @return $this
1396
     */
1397
    public function setEventDispatcher(Dispatcher $events)
1398
    {
1399
        $this->events = $events;
1400
 
1401
        return $this;
1402
    }
1403
 
1404
    /**
1405
     * Unset the event dispatcher for this connection.
1406
     *
1407
     * @return void
1408
     */
1409
    public function unsetEventDispatcher()
1410
    {
1411
        $this->events = null;
1412
    }
1413
 
1414
    /**
1415
     * Set the transaction manager instance on the connection.
1416
     *
1417
     * @param  \Illuminate\Database\DatabaseTransactionsManager  $manager
1418
     * @return $this
1419
     */
1420
    public function setTransactionManager($manager)
1421
    {
1422
        $this->transactionsManager = $manager;
1423
 
1424
        return $this;
1425
    }
1426
 
1427
    /**
1428
     * Unset the transaction manager for this connection.
1429
     *
1430
     * @return void
1431
     */
1432
    public function unsetTransactionManager()
1433
    {
1434
        $this->transactionsManager = null;
1435
    }
1436
 
1437
    /**
1438
     * Determine if the connection is in a "dry run".
1439
     *
1440
     * @return bool
1441
     */
1442
    public function pretending()
1443
    {
1444
        return $this->pretending === true;
1445
    }
1446
 
1447
    /**
1448
     * Get the connection query log.
1449
     *
1450
     * @return array
1451
     */
1452
    public function getQueryLog()
1453
    {
1454
        return $this->queryLog;
1455
    }
1456
 
1457
    /**
1458
     * Clear the query log.
1459
     *
1460
     * @return void
1461
     */
1462
    public function flushQueryLog()
1463
    {
1464
        $this->queryLog = [];
1465
    }
1466
 
1467
    /**
1468
     * Enable the query log on the connection.
1469
     *
1470
     * @return void
1471
     */
1472
    public function enableQueryLog()
1473
    {
1474
        $this->loggingQueries = true;
1475
    }
1476
 
1477
    /**
1478
     * Disable the query log on the connection.
1479
     *
1480
     * @return void
1481
     */
1482
    public function disableQueryLog()
1483
    {
1484
        $this->loggingQueries = false;
1485
    }
1486
 
1487
    /**
1488
     * Determine whether we're logging queries.
1489
     *
1490
     * @return bool
1491
     */
1492
    public function logging()
1493
    {
1494
        return $this->loggingQueries;
1495
    }
1496
 
1497
    /**
1498
     * Get the name of the connected database.
1499
     *
1500
     * @return string
1501
     */
1502
    public function getDatabaseName()
1503
    {
1504
        return $this->database;
1505
    }
1506
 
1507
    /**
1508
     * Set the name of the connected database.
1509
     *
1510
     * @param  string  $database
1511
     * @return $this
1512
     */
1513
    public function setDatabaseName($database)
1514
    {
1515
        $this->database = $database;
1516
 
1517
        return $this;
1518
    }
1519
 
1520
    /**
1521
     * Set the read / write type of the connection.
1522
     *
1523
     * @param  string|null  $readWriteType
1524
     * @return $this
1525
     */
1526
    public function setReadWriteType($readWriteType)
1527
    {
1528
        $this->readWriteType = $readWriteType;
1529
 
1530
        return $this;
1531
    }
1532
 
1533
    /**
1534
     * Get the table prefix for the connection.
1535
     *
1536
     * @return string
1537
     */
1538
    public function getTablePrefix()
1539
    {
1540
        return $this->tablePrefix;
1541
    }
1542
 
1543
    /**
1544
     * Set the table prefix in use by the connection.
1545
     *
1546
     * @param  string  $prefix
1547
     * @return $this
1548
     */
1549
    public function setTablePrefix($prefix)
1550
    {
1551
        $this->tablePrefix = $prefix;
1552
 
1553
        $this->getQueryGrammar()->setTablePrefix($prefix);
1554
 
1555
        return $this;
1556
    }
1557
 
1558
    /**
1559
     * Set the table prefix and return the grammar.
1560
     *
1561
     * @param  \Illuminate\Database\Grammar  $grammar
1562
     * @return \Illuminate\Database\Grammar
1563
     */
1564
    public function withTablePrefix(Grammar $grammar)
1565
    {
1566
        $grammar->setTablePrefix($this->tablePrefix);
1567
 
1568
        return $grammar;
1569
    }
1570
 
1571
    /**
1572
     * Register a connection resolver.
1573
     *
1574
     * @param  string  $driver
1575
     * @param  \Closure  $callback
1576
     * @return void
1577
     */
1578
    public static function resolverFor($driver, Closure $callback)
1579
    {
1580
        static::$resolvers[$driver] = $callback;
1581
    }
1582
 
1583
    /**
1584
     * Get the connection resolver for the given driver.
1585
     *
1586
     * @param  string  $driver
1587
     * @return mixed
1588
     */
1589
    public static function getResolver($driver)
1590
    {
1591
        return static::$resolvers[$driver] ?? null;
1592
    }
1593
}