Completed
Branch feature/pre-split (713d19)
by Anton
03:04
created

Driver::rollbackTransaction()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 16
Code Lines 8

Duplication

Lines 16
Ratio 100 %

Importance

Changes 0
Metric Value
cc 3
eloc 8
nc 3
nop 0
dl 16
loc 16
rs 9.4285
c 0
b 0
f 0
1
<?php
2
/**
3
 * Spiral Framework.
4
 *
5
 * @license   MIT
6
 * @author    Anton Titov (Wolfy-J)
7
 */
8
9
namespace Spiral\Database\Entities;
10
11
use Psr\Log\LoggerInterface;
12
use Spiral\Cache\StoreInterface;
13
use Spiral\Core\FactoryInterface;
14
use Spiral\Database\Builders\DeleteQuery;
15
use Spiral\Database\Builders\InsertQuery;
16
use Spiral\Database\Builders\SelectQuery;
17
use Spiral\Database\Builders\UpdateQuery;
18
use Spiral\Database\Entities\Prototypes\PDODriver;
19
use Spiral\Database\Entities\Query\CachedResult;
20
use Spiral\Database\Exceptions\DriverException;
21
use Spiral\Database\Exceptions\QueryException;
22
use Spiral\Database\Schemas\Prototypes\AbstractTable;
23
24
/**
25
 * Driver abstraction is responsible for DBMS specific set of functions and used by Databases to
26
 * hide implementation specific functionality. Extends PDODriver and adds ability to create driver
27
 * specific query builders and schemas (basically operates like a factory).
28
 */
29
abstract class Driver extends PDODriver
30
{
31
    /**
32
     * Schema table class.
33
     */
34
    const TABLE_SCHEMA_CLASS = '';
35
36
    /**
37
     * Commander used to execute commands. :).
38
     */
39
    const COMMANDER = '';
40
41
    /**
42
     * Query compiler class.
43
     */
44
    const QUERY_COMPILER = '';
45
46
    /**
47
     * Transaction level (count of nested transactions). Not all drives can support nested
48
     * transactions.
49
     *
50
     * @var int
51
     */
52
    private $transactionLevel = 0;
53
54
    /**
55
     * Associated cache store, if any.
56
     *
57
     * @var StoreInterface
58
     */
59
    protected $cacheStore = null;
60
61
    /**
62
     * @var FactoryInterface
63
     */
64
    protected $factory = null;
65
66
    /**
67
     * @param string           $name
68
     * @param array            $options
69
     * @param FactoryInterface $factory Required to build instances of query builders and compilers.
70
     * @param StoreInterface   $store   Cache store associated with driver (optional).
71
     */
72
    public function __construct(
73
        string $name,
74
        array $options,
75
        FactoryInterface $factory,
76
        StoreInterface $store = null
77
    ) {
78
        parent::__construct($name, $options);
79
80
        $this->factory = $factory;
81
        $this->cacheStore = $store;
82
    }
83
84
    /**
85
     * Set cache store to be used by driver.
86
     *
87
     * @param StoreInterface $store
88
     *
89
     * @return self|$this
90
     */
91
    public function setStore(StoreInterface $store): Driver
92
    {
93
        $this->cacheStore = $store;
94
95
        return $this;
96
    }
97
98
    /**
99
     * Execute statement or fetch result from cache and return cached query iterator.
100
     *
101
     * @param string         $query
102
     * @param array          $parameters Parameters to be binded into query.
103
     * @param int            $lifetime   Cache lifetime in seconds.
104
     * @param string         $key        Cache key to be used to store query result.
105
     * @param StoreInterface $store      Cache store to store result in, if null default store will
106
     *                                   be used.
107
     *
108
     * @return CachedResult
109
     *
110
     * @throws DriverException
111
     * @throws QueryException
112
     */
113
    public function cachedQuery(
114
        string $query,
115
        array $parameters = [],
116
        int $lifetime,
117
        string $key = '',
118
        StoreInterface $store = null
119
    ) {
120
        if (empty($store)) {
121
            if (empty($this->cacheStore)) {
122
                throw new DriverException("StoreInterface is missing");
123
            }
124
125
            $store = $this->cacheStore;
126
        }
127
128
        if (empty($key)) {
129
            //Trying to build unique query id based on provided options and environment.
130
            $key = md5(serialize([$query, $parameters, $this->getName()]));
131
        }
132
133
        $data = $store->remember($key, $lifetime, function () use ($query, $parameters) {
134
            return $this->query($query, $parameters)->fetchAll();
135
        });
136
137
        return new CachedResult($data, $parameters, $query, $key, $store);
138
    }
139
140
    /**
141
     * Check if table exists.
142
     *
143
     * @param string $name
144
     *
145
     * @return bool
146
     */
147
    abstract public function hasTable(string $name): bool;
148
149
    /**
150
     * Clean (truncate) specified driver table.
151
     *
152
     * @param string $table Table name with prefix included.
153
     */
154
    abstract public function truncateData(string $table);
155
156
    /**
157
     * Get every available table name as array.
158
     *
159
     * @return array
160
     */
161
    abstract public function tableNames(): array;
162
163
    /**
164
     * Get Driver specific AbstractTable implementation.
165
     *
166
     * @param string $table  Table name without prefix included.
167
     * @param string $prefix Database specific table prefix, this parameter is not required,
168
     *                       but if provided all
169
     *                       foreign keys will be created using it.
170
     *
171
     * @return AbstractTable
172
     */
173
    public function tableSchema(string $table, string $prefix = ''): AbstractTable
174
    {
175
        return $this->factory->make(
176
            static::TABLE_SCHEMA_CLASS,
177
            ['driver' => $this, 'name' => $table, 'prefix' => $prefix]
178
        );
179
    }
180
181
    /**
182
     * Get instance of Driver specific QueryCompiler.
183
     *
184
     * @param string $prefix Database specific table prefix, used to quote table names and build
185
     *                       aliases.
186
     *
187
     * @return QueryCompiler
188
     */
189
    public function queryCompiler(string $prefix = ''): QueryCompiler
190
    {
191
        return $this->factory->make(
192
            static::QUERY_COMPILER,
193
            ['driver' => $this, 'quoter' => new Quoter($this, $prefix)]
194
        );
195
    }
196
197
    /**
198
     * Get InsertQuery builder with driver specific query compiler.
199
     *
200
     * @param string $prefix     Database specific table prefix, used to quote table names and build
201
     *                           aliases.
202
     * @param array  $parameters Initial builder parameters.
203
     *
204
     * @return InsertQuery
205
     */
206 View Code Duplication
    public function insertBuilder(string $prefix, array $parameters = []): InsertQuery
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
207
    {
208
        return $this->factory->make(
209
            InsertQuery::class,
210
            ['driver' => $this, 'compiler' => $this->queryCompiler($prefix)] + $parameters
211
        );
212
    }
213
214
    /**
215
     * Get SelectQuery builder with driver specific query compiler.
216
     *
217
     * @param string $prefix     Database specific table prefix, used to quote table names and build
218
     *                           aliases.
219
     * @param array  $parameters Initial builder parameters.
220
     *
221
     * @return SelectQuery
222
     */
223 View Code Duplication
    public function selectBuilder(string $prefix, array $parameters = []): SelectQuery
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
224
    {
225
        return $this->factory->make(
226
            SelectQuery::class,
227
            ['driver' => $this, 'compiler' => $this->queryCompiler($prefix)] + $parameters
228
        );
229
    }
230
231
    /**
232
     * Get DeleteQuery builder with driver specific query compiler.
233
     *
234
     * @param string $prefix     Database specific table prefix, used to quote table names and build
235
     *                           aliases.
236
     * @param array  $parameters Initial builder parameters.
237
     *
238
     * @return DeleteQuery
239
     */
240 View Code Duplication
    public function deleteBuilder(string $prefix, array $parameters = []): DeleteQuery
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
241
    {
242
        return $this->factory->make(
243
            DeleteQuery::class,
244
            ['driver' => $this, 'compiler' => $this->queryCompiler($prefix)] + $parameters
245
        );
246
    }
247
248
    /**
249
     * Get UpdateQuery builder with driver specific query compiler.
250
     *
251
     * @param string $prefix     Database specific table prefix, used to quote table names and build
252
     *                           aliases.
253
     * @param array  $parameters Initial builder parameters.
254
     *
255
     * @return UpdateQuery
256
     */
257 View Code Duplication
    public function updateBuilder(string $prefix, array $parameters = []): UpdateQuery
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
258
    {
259
        return $this->factory->make(
260
            UpdateQuery::class,
261
            ['driver' => $this, 'compiler' => $this->queryCompiler($prefix)] + $parameters
262
        );
263
    }
264
265
    /**
266
     * Handler responsible for schema related operations. Handlers responsible for sync flow of
267
     * tables and columns, provide logger to aggregate all logger operations.
268
     *
269
     * @param LoggerInterface $logger
270
     *
271
     * @return AbstractHandler
272
     */
273
    abstract public function getHandler(LoggerInterface $logger = null): AbstractHandler;
274
275
    /**
276
     * Start SQL transaction with specified isolation level (not all DBMS support it). Nested
277
     * transactions are processed using savepoints.
278
     *
279
     * @link   http://en.wikipedia.org/wiki/Database_transaction
280
     * @link   http://en.wikipedia.org/wiki/Isolation_(database_systems)
281
     *
282
     * @param string $isolationLevel
283
     *
284
     * @return bool
285
     */
286
    public function beginTransaction(string $isolationLevel = null): bool
287
    {
288
        ++$this->transactionLevel;
289
290
        if ($this->transactionLevel == 1) {
291
            if (!empty($isolationLevel)) {
292
                $this->isolationLevel($isolationLevel);
293
            }
294
295
            if ($this->isProfiling()) {
296
                $this->logger()->info('Begin transaction');
297
            }
298
299
            return $this->getPDO()->beginTransaction();
300
        }
301
302
        $this->savepointCreate($this->transactionLevel);
303
304
        return true;
305
    }
306
307
    /**
308
     * Commit the active database transaction.
309
     *
310
     * @return bool
311
     */
312 View Code Duplication
    public function commitTransaction(): bool
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
313
    {
314
        --$this->transactionLevel;
315
316
        if ($this->transactionLevel == 0) {
317
            if ($this->isProfiling()) {
318
                $this->logger()->info('Commit transaction');
319
            }
320
321
            return $this->getPDO()->commit();
322
        }
323
324
        $this->savepointRelease($this->transactionLevel + 1);
325
326
        return true;
327
    }
328
329
    /**
330
     * Rollback the active database transaction.
331
     *
332
     * @return bool
333
     */
334 View Code Duplication
    public function rollbackTransaction(): bool
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
335
    {
336
        --$this->transactionLevel;
337
338
        if ($this->transactionLevel == 0) {
339
            if ($this->isProfiling()) {
340
                $this->logger()->info('Rollback transaction');
341
            }
342
343
            return $this->getPDO()->rollBack();
344
        }
345
346
        $this->savepointRollback($this->transactionLevel + 1);
347
348
        return true;
349
    }
350
351
    /**
352
     * Set transaction isolation level, this feature may not be supported by specific database
353
     * driver.
354
     *
355
     * @param string $level
356
     */
357
    protected function isolationLevel(string $level)
358
    {
359
        if ($this->isProfiling()) {
360
            $this->logger()->info("Set transaction isolation level to '{$level}'");
361
        }
362
363
        if (!empty($level)) {
364
            $this->statement("SET TRANSACTION ISOLATION LEVEL {$level}");
365
        }
366
    }
367
368
    /**
369
     * Create nested transaction save point.
370
     *
371
     * @link http://en.wikipedia.org/wiki/Savepoint
372
     *
373
     * @param string $name Savepoint name/id, must not contain spaces and be valid database
374
     *                     identifier.
375
     */
376 View Code Duplication
    protected function savepointCreate(string $name)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
377
    {
378
        if ($this->isProfiling()) {
379
            $this->logger()->info("Creating savepoint '{$name}'");
380
        }
381
382
        $this->statement('SAVEPOINT ' . $this->identifier("SVP{$name}"));
383
    }
384
385
    /**
386
     * Commit/release savepoint.
387
     *
388
     * @link http://en.wikipedia.org/wiki/Savepoint
389
     *
390
     * @param string $name Savepoint name/id, must not contain spaces and be valid database
391
     *                     identifier.
392
     */
393 View Code Duplication
    protected function savepointRelease(string $name)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
394
    {
395
        if ($this->isProfiling()) {
396
            $this->logger()->info("Releasing savepoint '{$name}'");
397
        }
398
399
        $this->statement('RELEASE SAVEPOINT ' . $this->identifier("SVP{$name}"));
400
    }
401
402
    /**
403
     * Rollback savepoint.
404
     *
405
     * @link http://en.wikipedia.org/wiki/Savepoint
406
     *
407
     * @param string $name Savepoint name/id, must not contain spaces and be valid database
408
     *                     identifier.
409
     */
410 View Code Duplication
    protected function savepointRollback(string $name)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
411
    {
412
        if ($this->isProfiling()) {
413
            $this->logger()->info("Rolling back savepoint '{$name}'");
414
        }
415
        $this->statement('ROLLBACK TO SAVEPOINT ' . $this->identifier("SVP{$name}"));
416
    }
417
}
418