|
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 Interop\Container\ContainerInterface; |
|
12
|
|
|
use Psr\Log\LoggerInterface; |
|
13
|
|
|
use Spiral\Core\Container; |
|
14
|
|
|
use Spiral\Core\FactoryInterface; |
|
15
|
|
|
use Spiral\Database\Builders\DeleteQuery; |
|
16
|
|
|
use Spiral\Database\Builders\InsertQuery; |
|
17
|
|
|
use Spiral\Database\Builders\SelectQuery; |
|
18
|
|
|
use Spiral\Database\Builders\UpdateQuery; |
|
19
|
|
|
use Spiral\Database\Schemas\Prototypes\AbstractTable; |
|
20
|
|
|
|
|
21
|
|
|
/** |
|
22
|
|
|
* Driver abstraction is responsible for DBMS specific set of functions and used by Databases to |
|
23
|
|
|
* hide implementation specific functionality. Extends PDODriver and adds ability to create driver |
|
24
|
|
|
* specific query builders and schemas (basically operates like a factory). |
|
25
|
|
|
*/ |
|
26
|
|
|
abstract class Driver extends PDODriver |
|
27
|
|
|
{ |
|
28
|
|
|
/** |
|
29
|
|
|
* Schema table class. |
|
30
|
|
|
*/ |
|
31
|
|
|
const TABLE_SCHEMA_CLASS = ''; |
|
32
|
|
|
|
|
33
|
|
|
/** |
|
34
|
|
|
* Commander used to execute commands. :). |
|
35
|
|
|
*/ |
|
36
|
|
|
const COMMANDER = ''; |
|
37
|
|
|
|
|
38
|
|
|
/** |
|
39
|
|
|
* Query compiler class. |
|
40
|
|
|
*/ |
|
41
|
|
|
const QUERY_COMPILER = ''; |
|
42
|
|
|
|
|
43
|
|
|
/** |
|
44
|
|
|
* Transaction level (count of nested transactions). Not all drives can support nested |
|
45
|
|
|
* transactions. |
|
46
|
|
|
* |
|
47
|
|
|
* @var int |
|
48
|
|
|
*/ |
|
49
|
|
|
private $transactionLevel = 0; |
|
50
|
|
|
|
|
51
|
|
|
/** |
|
52
|
|
|
* Defines IoC scope for all driver specific builders. |
|
53
|
|
|
* |
|
54
|
|
|
* @var ContainerInterface |
|
55
|
|
|
*/ |
|
56
|
|
|
protected $container = null; |
|
57
|
|
|
|
|
58
|
|
|
/** |
|
59
|
|
|
* @param string $name |
|
60
|
|
|
* @param array $options |
|
61
|
|
|
* @param ContainerInterface $container Required to build instances of query builders and |
|
62
|
|
|
* compilers. Also provides support for scope specific |
|
63
|
|
|
* functionality like magic paginators and logs (yes, you |
|
64
|
|
|
* can store LogsInterface in this container set profiling |
|
65
|
|
|
* listener. |
|
66
|
|
|
*/ |
|
67
|
|
|
public function __construct(string $name, array $options, ContainerInterface $container = null) |
|
68
|
|
|
{ |
|
69
|
|
|
parent::__construct($name, $options); |
|
70
|
|
|
|
|
71
|
|
|
//Factory with default fallback |
|
72
|
|
|
$this->container = $container ?? new Container(); |
|
73
|
|
|
} |
|
74
|
|
|
|
|
75
|
|
|
/** |
|
76
|
|
|
* Check if table exists. |
|
77
|
|
|
* |
|
78
|
|
|
* @param string $name |
|
79
|
|
|
* |
|
80
|
|
|
* @return bool |
|
81
|
|
|
*/ |
|
82
|
|
|
abstract public function hasTable(string $name): bool; |
|
83
|
|
|
|
|
84
|
|
|
/** |
|
85
|
|
|
* Clean (truncate) specified driver table. |
|
86
|
|
|
* |
|
87
|
|
|
* @param string $table Table name with prefix included. |
|
88
|
|
|
*/ |
|
89
|
|
|
abstract public function truncateData(string $table); |
|
90
|
|
|
|
|
91
|
|
|
/** |
|
92
|
|
|
* Get every available table name as array. |
|
93
|
|
|
* |
|
94
|
|
|
* @return array |
|
95
|
|
|
*/ |
|
96
|
|
|
abstract public function tableNames(): array; |
|
97
|
|
|
|
|
98
|
|
|
/** |
|
99
|
|
|
* Get Driver specific AbstractTable implementation. |
|
100
|
|
|
* |
|
101
|
|
|
* @param string $table Table name without prefix included. |
|
102
|
|
|
* @param string $prefix Database specific table prefix, this parameter is not required, |
|
103
|
|
|
* but if provided all |
|
104
|
|
|
* foreign keys will be created using it. |
|
105
|
|
|
* |
|
106
|
|
|
* @return AbstractTable |
|
107
|
|
|
*/ |
|
108
|
|
|
public function tableSchema(string $table, string $prefix = ''): AbstractTable |
|
109
|
|
|
{ |
|
110
|
|
|
return $this->getFactory()->make( |
|
111
|
|
|
static::TABLE_SCHEMA_CLASS, |
|
112
|
|
|
['driver' => $this, 'name' => $table, 'prefix' => $prefix] |
|
113
|
|
|
); |
|
114
|
|
|
} |
|
115
|
|
|
|
|
116
|
|
|
/** |
|
117
|
|
|
* Get instance of Driver specific QueryCompiler. |
|
118
|
|
|
* |
|
119
|
|
|
* @param string $prefix Database specific table prefix, used to quote table names and build |
|
120
|
|
|
* aliases. |
|
121
|
|
|
* |
|
122
|
|
|
* @return QueryCompiler |
|
123
|
|
|
*/ |
|
124
|
|
|
public function queryCompiler(string $prefix = ''): QueryCompiler |
|
125
|
|
|
{ |
|
126
|
|
|
return $this->getFactory()->make( |
|
127
|
|
|
static::QUERY_COMPILER, |
|
128
|
|
|
['driver' => $this, 'quoter' => new Quoter($this, $prefix)] |
|
129
|
|
|
); |
|
130
|
|
|
} |
|
131
|
|
|
|
|
132
|
|
|
/** |
|
133
|
|
|
* Get InsertQuery builder with driver specific query compiler. |
|
134
|
|
|
* |
|
135
|
|
|
* @param string $prefix Database specific table prefix, used to quote table names and build |
|
136
|
|
|
* aliases. |
|
137
|
|
|
* @param array $parameters Initial builder parameters. |
|
138
|
|
|
* |
|
139
|
|
|
* @return InsertQuery |
|
140
|
|
|
*/ |
|
141
|
|
View Code Duplication |
public function insertBuilder(string $prefix, array $parameters = []): InsertQuery |
|
|
|
|
|
|
142
|
|
|
{ |
|
143
|
|
|
return $this->getFactory()->make( |
|
144
|
|
|
InsertQuery::class, |
|
145
|
|
|
['driver' => $this, 'compiler' => $this->queryCompiler($prefix)] + $parameters |
|
146
|
|
|
); |
|
147
|
|
|
} |
|
148
|
|
|
|
|
149
|
|
|
/** |
|
150
|
|
|
* Get SelectQuery builder with driver specific query compiler. |
|
151
|
|
|
* |
|
152
|
|
|
* @param string $prefix Database specific table prefix, used to quote table names and build |
|
153
|
|
|
* aliases. |
|
154
|
|
|
* @param array $parameters Initial builder parameters. |
|
155
|
|
|
* |
|
156
|
|
|
* @return SelectQuery |
|
157
|
|
|
*/ |
|
158
|
|
View Code Duplication |
public function selectBuilder(string $prefix, array $parameters = []): SelectQuery |
|
|
|
|
|
|
159
|
|
|
{ |
|
160
|
|
|
return $this->getFactory()->make( |
|
161
|
|
|
SelectQuery::class, |
|
162
|
|
|
['driver' => $this, 'compiler' => $this->queryCompiler($prefix)] + $parameters |
|
163
|
|
|
); |
|
164
|
|
|
} |
|
165
|
|
|
|
|
166
|
|
|
/** |
|
167
|
|
|
* Get DeleteQuery builder with driver specific query compiler. |
|
168
|
|
|
* |
|
169
|
|
|
* @param string $prefix Database specific table prefix, used to quote table names and build |
|
170
|
|
|
* aliases. |
|
171
|
|
|
* @param array $parameters Initial builder parameters. |
|
172
|
|
|
* |
|
173
|
|
|
* @return DeleteQuery |
|
174
|
|
|
*/ |
|
175
|
|
View Code Duplication |
public function deleteBuilder(string $prefix, array $parameters = []): DeleteQuery |
|
|
|
|
|
|
176
|
|
|
{ |
|
177
|
|
|
return $this->getFactory()->make( |
|
178
|
|
|
DeleteQuery::class, |
|
179
|
|
|
['driver' => $this, 'compiler' => $this->queryCompiler($prefix)] + $parameters |
|
180
|
|
|
); |
|
181
|
|
|
} |
|
182
|
|
|
|
|
183
|
|
|
/** |
|
184
|
|
|
* Get UpdateQuery builder with driver specific query compiler. |
|
185
|
|
|
* |
|
186
|
|
|
* @param string $prefix Database specific table prefix, used to quote table names and build |
|
187
|
|
|
* aliases. |
|
188
|
|
|
* @param array $parameters Initial builder parameters. |
|
189
|
|
|
* |
|
190
|
|
|
* @return UpdateQuery |
|
191
|
|
|
*/ |
|
192
|
|
View Code Duplication |
public function updateBuilder(string $prefix, array $parameters = []): UpdateQuery |
|
|
|
|
|
|
193
|
|
|
{ |
|
194
|
|
|
return $this->getFactory()->make( |
|
195
|
|
|
UpdateQuery::class, |
|
196
|
|
|
['driver' => $this, 'compiler' => $this->queryCompiler($prefix)] + $parameters |
|
197
|
|
|
); |
|
198
|
|
|
} |
|
199
|
|
|
|
|
200
|
|
|
/** |
|
201
|
|
|
* Handler responsible for schema related operations. Handlers responsible for sync flow of |
|
202
|
|
|
* tables and columns, provide logger to aggregate all logger operations. |
|
203
|
|
|
* |
|
204
|
|
|
* @param LoggerInterface $logger |
|
205
|
|
|
* |
|
206
|
|
|
* @return AbstractHandler |
|
207
|
|
|
*/ |
|
208
|
|
|
abstract public function getHandler(LoggerInterface $logger = null): AbstractHandler; |
|
209
|
|
|
|
|
210
|
|
|
/** |
|
211
|
|
|
* Start SQL transaction with specified isolation level (not all DBMS support it). Nested |
|
212
|
|
|
* transactions are processed using savepoints. |
|
213
|
|
|
* |
|
214
|
|
|
* @link http://en.wikipedia.org/wiki/Database_transaction |
|
215
|
|
|
* @link http://en.wikipedia.org/wiki/Isolation_(database_systems) |
|
216
|
|
|
* |
|
217
|
|
|
* @param string $isolationLevel |
|
218
|
|
|
* |
|
219
|
|
|
* @return bool |
|
220
|
|
|
*/ |
|
221
|
|
|
public function beginTransaction(string $isolationLevel = null): bool |
|
222
|
|
|
{ |
|
223
|
|
|
++$this->transactionLevel; |
|
224
|
|
|
|
|
225
|
|
|
if ($this->transactionLevel == 1) { |
|
226
|
|
|
if (!empty($isolationLevel)) { |
|
227
|
|
|
$this->isolationLevel($isolationLevel); |
|
228
|
|
|
} |
|
229
|
|
|
|
|
230
|
|
|
if ($this->isProfiling()) { |
|
231
|
|
|
$this->logger()->info('Begin transaction'); |
|
232
|
|
|
} |
|
233
|
|
|
|
|
234
|
|
|
return $this->getPDO()->beginTransaction(); |
|
235
|
|
|
} |
|
236
|
|
|
|
|
237
|
|
|
$this->savepointCreate($this->transactionLevel); |
|
238
|
|
|
|
|
239
|
|
|
return true; |
|
240
|
|
|
} |
|
241
|
|
|
|
|
242
|
|
|
/** |
|
243
|
|
|
* Commit the active database transaction. |
|
244
|
|
|
* |
|
245
|
|
|
* @return bool |
|
246
|
|
|
*/ |
|
247
|
|
View Code Duplication |
public function commitTransaction(): bool |
|
|
|
|
|
|
248
|
|
|
{ |
|
249
|
|
|
--$this->transactionLevel; |
|
250
|
|
|
|
|
251
|
|
|
if ($this->transactionLevel == 0) { |
|
252
|
|
|
if ($this->isProfiling()) { |
|
253
|
|
|
$this->logger()->info('Commit transaction'); |
|
254
|
|
|
} |
|
255
|
|
|
|
|
256
|
|
|
return $this->getPDO()->commit(); |
|
257
|
|
|
} |
|
258
|
|
|
|
|
259
|
|
|
$this->savepointRelease($this->transactionLevel + 1); |
|
260
|
|
|
|
|
261
|
|
|
return true; |
|
262
|
|
|
} |
|
263
|
|
|
|
|
264
|
|
|
/** |
|
265
|
|
|
* Rollback the active database transaction. |
|
266
|
|
|
* |
|
267
|
|
|
* @return bool |
|
268
|
|
|
*/ |
|
269
|
|
View Code Duplication |
public function rollbackTransaction(): bool |
|
|
|
|
|
|
270
|
|
|
{ |
|
271
|
|
|
--$this->transactionLevel; |
|
272
|
|
|
|
|
273
|
|
|
if ($this->transactionLevel == 0) { |
|
274
|
|
|
if ($this->isProfiling()) { |
|
275
|
|
|
$this->logger()->info('Rollback transaction'); |
|
276
|
|
|
} |
|
277
|
|
|
|
|
278
|
|
|
return $this->getPDO()->rollBack(); |
|
279
|
|
|
} |
|
280
|
|
|
|
|
281
|
|
|
$this->savepointRollback($this->transactionLevel + 1); |
|
282
|
|
|
|
|
283
|
|
|
return true; |
|
284
|
|
|
} |
|
285
|
|
|
|
|
286
|
|
|
/** |
|
287
|
|
|
* Get driver specific factory. |
|
288
|
|
|
* |
|
289
|
|
|
* @return FactoryInterface |
|
290
|
|
|
*/ |
|
291
|
|
|
protected function getFactory(): FactoryInterface |
|
292
|
|
|
{ |
|
293
|
|
|
if ($this->container instanceof FactoryInterface) { |
|
294
|
|
|
return $this->container; |
|
295
|
|
|
} |
|
296
|
|
|
|
|
297
|
|
|
return $this->container->get(FactoryInterface::class); |
|
298
|
|
|
} |
|
299
|
|
|
|
|
300
|
|
|
/** |
|
301
|
|
|
* Set transaction isolation level, this feature may not be supported by specific database |
|
302
|
|
|
* driver. |
|
303
|
|
|
* |
|
304
|
|
|
* @param string $level |
|
305
|
|
|
*/ |
|
306
|
|
|
protected function isolationLevel(string $level) |
|
307
|
|
|
{ |
|
308
|
|
|
if ($this->isProfiling()) { |
|
309
|
|
|
$this->logger()->info("Set transaction isolation level to '{$level}'"); |
|
310
|
|
|
} |
|
311
|
|
|
|
|
312
|
|
|
if (!empty($level)) { |
|
313
|
|
|
$this->statement("SET TRANSACTION ISOLATION LEVEL {$level}"); |
|
314
|
|
|
} |
|
315
|
|
|
} |
|
316
|
|
|
|
|
317
|
|
|
/** |
|
318
|
|
|
* Create nested transaction save point. |
|
319
|
|
|
* |
|
320
|
|
|
* @link http://en.wikipedia.org/wiki/Savepoint |
|
321
|
|
|
* |
|
322
|
|
|
* @param string $name Savepoint name/id, must not contain spaces and be valid database |
|
323
|
|
|
* identifier. |
|
324
|
|
|
*/ |
|
325
|
|
View Code Duplication |
protected function savepointCreate(string $name) |
|
|
|
|
|
|
326
|
|
|
{ |
|
327
|
|
|
if ($this->isProfiling()) { |
|
328
|
|
|
$this->logger()->info("Creating savepoint '{$name}'"); |
|
329
|
|
|
} |
|
330
|
|
|
|
|
331
|
|
|
$this->statement('SAVEPOINT ' . $this->identifier("SVP{$name}")); |
|
332
|
|
|
} |
|
333
|
|
|
|
|
334
|
|
|
/** |
|
335
|
|
|
* Commit/release savepoint. |
|
336
|
|
|
* |
|
337
|
|
|
* @link http://en.wikipedia.org/wiki/Savepoint |
|
338
|
|
|
* |
|
339
|
|
|
* @param string $name Savepoint name/id, must not contain spaces and be valid database |
|
340
|
|
|
* identifier. |
|
341
|
|
|
*/ |
|
342
|
|
View Code Duplication |
protected function savepointRelease(string $name) |
|
|
|
|
|
|
343
|
|
|
{ |
|
344
|
|
|
if ($this->isProfiling()) { |
|
345
|
|
|
$this->logger()->info("Releasing savepoint '{$name}'"); |
|
346
|
|
|
} |
|
347
|
|
|
|
|
348
|
|
|
$this->statement('RELEASE SAVEPOINT ' . $this->identifier("SVP{$name}")); |
|
349
|
|
|
} |
|
350
|
|
|
|
|
351
|
|
|
/** |
|
352
|
|
|
* Rollback savepoint. |
|
353
|
|
|
* |
|
354
|
|
|
* @link http://en.wikipedia.org/wiki/Savepoint |
|
355
|
|
|
* |
|
356
|
|
|
* @param string $name Savepoint name/id, must not contain spaces and be valid database |
|
357
|
|
|
* identifier. |
|
358
|
|
|
*/ |
|
359
|
|
View Code Duplication |
protected function savepointRollback(string $name) |
|
|
|
|
|
|
360
|
|
|
{ |
|
361
|
|
|
if ($this->isProfiling()) { |
|
362
|
|
|
$this->logger()->info("Rolling back savepoint '{$name}'"); |
|
363
|
|
|
} |
|
364
|
|
|
$this->statement('ROLLBACK TO SAVEPOINT ' . $this->identifier("SVP{$name}")); |
|
365
|
|
|
} |
|
366
|
|
|
} |
|
367
|
|
|
|
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.