1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* Spiral Framework, Core Components |
4
|
|
|
* |
5
|
|
|
* @author Wolfy-J |
6
|
|
|
*/ |
7
|
|
|
namespace Spiral\Database\Drivers\SQLite; |
8
|
|
|
|
9
|
|
|
use Spiral\Database\Entities\AbstractHandler; |
10
|
|
|
use Spiral\Database\Exceptions\HandlerException; |
11
|
|
|
use Spiral\Database\Schemas\Prototypes\AbstractColumn; |
12
|
|
|
use Spiral\Database\Schemas\Prototypes\AbstractReference; |
13
|
|
|
use Spiral\Database\Schemas\Prototypes\AbstractTable; |
14
|
|
|
|
15
|
|
|
class SQLiteHandler extends AbstractHandler |
16
|
|
|
{ |
17
|
|
|
/** |
18
|
|
|
* Drop table from database. |
19
|
|
|
* |
20
|
|
|
* @param AbstractTable $table |
21
|
|
|
* |
22
|
|
|
* @throws HandlerException |
23
|
|
|
*/ |
24
|
|
|
public function dropTable(AbstractTable $table) |
25
|
|
|
{ |
26
|
|
|
parent::dropTable($table); |
27
|
|
|
} |
28
|
|
|
|
29
|
|
|
/** |
30
|
|
|
* {@inheritdoc} |
31
|
|
|
*/ |
32
|
|
|
public function syncTable(AbstractTable $table, int $behaviour = self::DO_ALL) |
33
|
|
|
{ |
34
|
|
|
if (!$this->requiresRebuild($table)) { |
35
|
|
|
//Nothing special, can be handled as usually |
36
|
|
|
parent::syncTable($table, $behaviour); |
37
|
|
|
|
38
|
|
|
return; |
39
|
|
|
} |
40
|
|
|
|
41
|
|
|
//Now we have to work with temporary table in order to perform every change |
42
|
|
|
$this->log('Rebuilding table {table} to apply required modifications.', [ |
43
|
|
|
'table' => $this->identify($table) |
44
|
|
|
]); |
45
|
|
|
|
46
|
|
|
$initial = clone $table; |
47
|
|
|
$initial->resetState(); |
48
|
|
|
|
49
|
|
|
//Temporary table is required to copy data over |
50
|
|
|
$temporary = $this->createTemporary($table); |
51
|
|
|
|
52
|
|
|
//Moving data over |
53
|
|
|
$this->copyData( |
54
|
|
|
$initial->getName(), |
55
|
|
|
$temporary->getName(), |
56
|
|
|
$this->createMapping($initial, $temporary) |
57
|
|
|
); |
58
|
|
|
|
59
|
|
|
//We can drop initial table now |
60
|
|
|
$this->dropTable($table); |
61
|
|
|
|
62
|
|
|
//Renaming temporary table (should automatically handle table renaming) |
63
|
|
|
$this->renameTable($temporary->getName(), $initial->getName()); |
64
|
|
|
|
65
|
|
|
//Not all databases support adding index while table creation, so we can do it after |
66
|
|
|
foreach ($table->getIndexes() as $index) { |
67
|
|
|
$this->createIndex($table, $index); |
68
|
|
|
} |
69
|
|
|
} |
70
|
|
|
|
71
|
|
|
/** |
72
|
|
|
* {@inheritdoc} |
73
|
|
|
*/ |
74
|
|
|
public function createColumn(AbstractTable $table, AbstractColumn $column) |
75
|
|
|
{ |
76
|
|
|
//Not supported |
77
|
|
|
} |
78
|
|
|
|
79
|
|
|
/** |
80
|
|
|
* {@inheritdoc} |
81
|
|
|
*/ |
82
|
|
|
public function dropColumn(AbstractTable $table, AbstractColumn $column) |
83
|
|
|
{ |
84
|
|
|
//Not supported |
85
|
|
|
} |
86
|
|
|
|
87
|
|
|
/** |
88
|
|
|
* {@inheritdoc} |
89
|
|
|
*/ |
90
|
|
|
public function alterColumn( |
91
|
|
|
AbstractTable $table, |
92
|
|
|
AbstractColumn $initial, |
93
|
|
|
AbstractColumn $column |
94
|
|
|
) { |
95
|
|
|
//Not supported |
96
|
|
|
} |
97
|
|
|
|
98
|
|
|
/** |
99
|
|
|
* {@inheritdoc} |
100
|
|
|
*/ |
101
|
|
|
public function createForeign(AbstractTable $table, AbstractReference $foreign) |
102
|
|
|
{ |
103
|
|
|
//Not supported |
104
|
|
|
} |
105
|
|
|
|
106
|
|
|
/** |
107
|
|
|
* {@inheritdoc} |
108
|
|
|
*/ |
109
|
|
|
public function dropForeign(AbstractTable $table, AbstractReference $foreign) |
110
|
|
|
{ |
111
|
|
|
//Not supported |
112
|
|
|
} |
113
|
|
|
|
114
|
|
|
/** |
115
|
|
|
* {@inheritdoc} |
116
|
|
|
*/ |
117
|
|
|
public function alterForeign( |
118
|
|
|
AbstractTable $table, |
119
|
|
|
AbstractReference $initial, |
120
|
|
|
AbstractReference $foreign |
121
|
|
|
) { |
122
|
|
|
//Not supported |
123
|
|
|
} |
124
|
|
|
|
125
|
|
|
/** |
126
|
|
|
* Rebuild is required when columns or foreign keys are altered. |
127
|
|
|
* |
128
|
|
|
* @param AbstractTable $table |
129
|
|
|
* |
130
|
|
|
* @return bool |
131
|
|
|
*/ |
132
|
|
|
private function requiresRebuild(AbstractTable $table): bool |
133
|
|
|
{ |
134
|
|
|
$comparator = $table->getComparator(); |
135
|
|
|
|
136
|
|
|
// if ($comparator->isRenamed()) { |
|
|
|
|
137
|
|
|
// return true; |
138
|
|
|
// } |
139
|
|
|
|
140
|
|
|
$difference = [ |
141
|
|
|
count($comparator->addedColumns()), |
142
|
|
|
count($comparator->droppedColumns()), |
143
|
|
|
count($comparator->alteredColumns()), |
144
|
|
|
count($comparator->addedForeigns()), |
145
|
|
|
count($comparator->droppedForeigns()), |
146
|
|
|
count($comparator->alteredForeigns()), |
147
|
|
|
]; |
148
|
|
|
|
149
|
|
|
return array_sum($difference) != 0; |
150
|
|
|
} |
151
|
|
|
|
152
|
|
|
/** |
153
|
|
|
* Temporary table based on parent. |
154
|
|
|
* |
155
|
|
|
* @param AbstractTable $table |
156
|
|
|
* |
157
|
|
|
* @return AbstractTable |
158
|
|
|
*/ |
159
|
|
|
protected function createTemporary(AbstractTable $table): AbstractTable |
160
|
|
|
{ |
161
|
|
|
//Temporary table is required to copy data over |
162
|
|
|
$temporary = clone $table; |
163
|
|
|
$temporary->setName('spiral_temp_' . $table->getName() . '_' . uniqid()); |
164
|
|
|
|
165
|
|
|
//We don't need any indexes in temporary table |
166
|
|
|
foreach ($temporary->getIndexes() as $index) { |
167
|
|
|
$temporary->dropIndex($index->getColumns()); |
168
|
|
|
} |
169
|
|
|
|
170
|
|
|
$this->createTable($temporary); |
171
|
|
|
|
172
|
|
|
return $temporary; |
173
|
|
|
} |
174
|
|
|
|
175
|
|
|
/** |
176
|
|
|
* Copy table data to another location. |
177
|
|
|
* |
178
|
|
|
* @see http://stackoverflow.com/questions/4007014/alter-column-in-sqlite |
179
|
|
|
* |
180
|
|
|
* @param string $source |
181
|
|
|
* @param string $to |
182
|
|
|
* @param array $mapping (destination => source) |
183
|
|
|
* |
184
|
|
|
* @throws HandlerException |
185
|
|
|
*/ |
186
|
|
|
private function copyData(string $source, string $to, array $mapping) |
187
|
|
|
{ |
188
|
|
|
$sourceColumns = array_keys($mapping); |
189
|
|
|
$targetColumns = array_values($mapping); |
190
|
|
|
|
191
|
|
|
$this->log( |
192
|
|
|
'Copying table data from {source} to {to} using mapping ({columns}) => ({target}).', |
193
|
|
|
[ |
194
|
|
|
'source' => $this->identify($source), |
195
|
|
|
'to' => $this->identify($to), |
196
|
|
|
'columns' => implode(', ', $sourceColumns), |
197
|
|
|
'target' => implode(', ', $targetColumns), |
198
|
|
|
] |
199
|
|
|
); |
200
|
|
|
|
201
|
|
|
//Preparing mapping |
202
|
|
|
$sourceColumns = array_map([$this, 'identify'], $sourceColumns); |
203
|
|
|
$targetColumns = array_map([$this, 'identify'], $targetColumns); |
204
|
|
|
|
205
|
|
|
$query = \Spiral\interpolate('INSERT INTO {to} ({target}) SELECT {columns} FROM {source}', |
206
|
|
|
[ |
207
|
|
|
'source' => $this->identify($source), |
208
|
|
|
'to' => $this->identify($to), |
209
|
|
|
'columns' => implode(', ', $sourceColumns), |
210
|
|
|
'target' => implode(', ', $targetColumns), |
211
|
|
|
] |
212
|
|
|
); |
213
|
|
|
|
214
|
|
|
$this->run($query); |
215
|
|
|
} |
216
|
|
|
|
217
|
|
|
/** |
218
|
|
|
* Get mapping between new and initial columns. |
219
|
|
|
* |
220
|
|
|
* @param AbstractTable $source |
221
|
|
|
* @param AbstractTable $target |
222
|
|
|
* |
223
|
|
|
* @return array |
224
|
|
|
*/ |
225
|
|
|
private function createMapping(AbstractTable $source, AbstractTable $target) |
226
|
|
|
{ |
227
|
|
|
$mapping = []; |
228
|
|
|
foreach ($target->getColumns() as $name => $column) { |
229
|
|
|
if ($source->hasColumn($name)) { |
230
|
|
|
$mapping[$name] = $column->getName(); |
231
|
|
|
} |
232
|
|
|
} |
233
|
|
|
|
234
|
|
|
return $mapping; |
235
|
|
|
} |
236
|
|
|
} |
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.