1 | <?php |
||||||
2 | |||||||
3 | /** |
||||||
4 | * This file is part of Cycle ORM package. |
||||||
5 | * |
||||||
6 | * For the full copyright and license information, please view the LICENSE |
||||||
7 | * file that was distributed with this source code. |
||||||
8 | */ |
||||||
9 | |||||||
10 | declare(strict_types=1); |
||||||
11 | |||||||
12 | namespace Cycle\Database\Driver\SQLite; |
||||||
13 | |||||||
14 | use Cycle\Database\Driver\Handler; |
||||||
15 | use Cycle\Database\Driver\SQLite\Schema\SQLiteTable; |
||||||
16 | use Cycle\Database\Exception\DBALException; |
||||||
17 | use Cycle\Database\Exception\HandlerException; |
||||||
18 | use Cycle\Database\Schema\AbstractColumn; |
||||||
0 ignored issues
–
show
|
|||||||
19 | use Cycle\Database\Schema\AbstractForeignKey; |
||||||
20 | use Cycle\Database\Schema\AbstractTable; |
||||||
21 | |||||||
22 | class SQLiteHandler extends Handler |
||||||
23 | { |
||||||
24 | /** |
||||||
25 | * @return string[] |
||||||
26 | */ |
||||||
27 | 904 | public function getTableNames(string $prefix = ''): array |
|||||
28 | { |
||||||
29 | 904 | $query = $this->driver->query( |
|||||
0 ignored issues
–
show
The method
query() does not exist on null .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces. This is most likely a typographical error or the method has been renamed. ![]() |
|||||||
30 | 904 | "SELECT name FROM 'sqlite_master' WHERE type = 'table'", |
|||||
31 | ); |
||||||
32 | |||||||
33 | 904 | $tables = []; |
|||||
34 | 904 | foreach ($query as $table) { |
|||||
35 | 448 | if ($table['name'] === 'sqlite_sequence') { |
|||||
36 | continue; |
||||||
37 | } |
||||||
38 | |||||||
39 | 448 | if ($prefix !== '' && !\str_starts_with($table['name'], $prefix)) { |
|||||
40 | 2 | continue; |
|||||
41 | } |
||||||
42 | |||||||
43 | 448 | $tables[] = $table['name']; |
|||||
44 | } |
||||||
45 | |||||||
46 | 904 | return $tables; |
|||||
47 | } |
||||||
48 | |||||||
49 | /** |
||||||
50 | * @psalm-param non-empty-string $table |
||||||
51 | */ |
||||||
52 | 482 | public function hasTable(string $table): bool |
|||||
53 | { |
||||||
54 | 482 | $query = "SELECT COUNT('sql') FROM 'sqlite_master' WHERE type = 'table' and name = ?"; |
|||||
55 | |||||||
56 | 482 | return (bool) $this->driver->query($query, [$table])->fetchColumn(); |
|||||
57 | } |
||||||
58 | |||||||
59 | 482 | public function getSchema(string $table, ?string $prefix = null): AbstractTable |
|||||
60 | { |
||||||
61 | 482 | return new SQLiteTable($this->driver, $table, $prefix ?? ''); |
|||||
0 ignored issues
–
show
It seems like
$this->driver can also be of type null ; however, parameter $driver of Cycle\Database\Driver\SQ...iteTable::__construct() does only seem to accept Cycle\Database\Driver\DriverInterface , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
62 | } |
||||||
63 | |||||||
64 | 4 | public function eraseTable(AbstractTable $table): void |
|||||
65 | { |
||||||
66 | 4 | $this->driver->execute( |
|||||
67 | 4 | "DELETE FROM {$this->driver->identifier($table->getFullName())}", |
|||||
0 ignored issues
–
show
The method
identifier() does not exist on Cycle\Database\Driver\DriverInterface . It seems like you code against a sub-type of Cycle\Database\Driver\DriverInterface such as Cycle\Database\Driver\Driver .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
68 | ); |
||||||
69 | 2 | } |
|||||
70 | |||||||
71 | 158 | public function syncTable(AbstractTable $table, int $operation = self::DO_ALL): void |
|||||
72 | { |
||||||
73 | 158 | if (!$this->requiresRebuild($table)) { |
|||||
74 | //Nothing special, can be handled as usually |
||||||
75 | 50 | parent::syncTable($table, $operation); |
|||||
76 | |||||||
77 | 50 | return; |
|||||
78 | } |
||||||
79 | |||||||
80 | 116 | if ($table->getComparator()->isPrimaryChanged()) { |
|||||
81 | throw new DBALException('Unable to change primary keys for existed table'); |
||||||
82 | } |
||||||
83 | |||||||
84 | 116 | $initial = clone $table; |
|||||
85 | 116 | $initial->resetState(); |
|||||
86 | |||||||
87 | //Temporary table is required to copy data over |
||||||
88 | 116 | $temporary = $this->createTemporary($table); |
|||||
89 | |||||||
90 | //Moving data over |
||||||
91 | 116 | $this->copyData( |
|||||
92 | 116 | $initial->getFullName(), |
|||||
93 | 116 | $temporary->getFullName(), |
|||||
94 | 116 | $this->createMapping($initial, $temporary), |
|||||
95 | ); |
||||||
96 | |||||||
97 | //We can drop initial table now |
||||||
98 | 116 | $this->dropTable($table); |
|||||
99 | |||||||
100 | //Renaming temporary table (should automatically handle table renaming) |
||||||
101 | 116 | $this->renameTable($temporary->getFullName(), $initial->getFullName()); |
|||||
102 | |||||||
103 | //Not all databases support adding index while table creation, so we can do it after |
||||||
104 | 116 | foreach ($table->getIndexes() as $index) { |
|||||
105 | 64 | $this->createIndex($table, $index); |
|||||
106 | } |
||||||
107 | 116 | } |
|||||
108 | |||||||
109 | public function createColumn(AbstractTable $table, AbstractColumn $column): void |
||||||
110 | { |
||||||
111 | //Not supported |
||||||
112 | } |
||||||
113 | |||||||
114 | public function dropColumn(AbstractTable $table, AbstractColumn $column): void |
||||||
115 | { |
||||||
116 | //Not supported |
||||||
117 | } |
||||||
118 | |||||||
119 | public function alterColumn( |
||||||
120 | AbstractTable $table, |
||||||
121 | AbstractColumn $initial, |
||||||
122 | AbstractColumn $column, |
||||||
123 | ): void { |
||||||
124 | //Not supported |
||||||
125 | } |
||||||
126 | |||||||
127 | public function createForeignKey(AbstractTable $table, AbstractForeignKey $foreignKey): void |
||||||
128 | { |
||||||
129 | //Not supported |
||||||
130 | } |
||||||
131 | |||||||
132 | public function dropForeignKey(AbstractTable $table, AbstractForeignKey $foreignKey): void |
||||||
133 | { |
||||||
134 | //Not supported |
||||||
135 | } |
||||||
136 | |||||||
137 | public function alterForeignKey( |
||||||
138 | AbstractTable $table, |
||||||
139 | AbstractForeignKey $initial, |
||||||
140 | AbstractForeignKey $foreignKey, |
||||||
141 | ): void { |
||||||
142 | //Not supported |
||||||
143 | } |
||||||
144 | |||||||
145 | public function enableForeignKeyConstraints(): void |
||||||
146 | { |
||||||
147 | $this->run('PRAGMA foreign_keys = ON;'); |
||||||
148 | 116 | } |
|||||
149 | |||||||
150 | public function disableForeignKeyConstraints(): void |
||||||
151 | 116 | { |
|||||
152 | 116 | $this->run('PRAGMA foreign_keys = OFF;'); |
|||||
153 | 116 | } |
|||||
154 | |||||||
155 | /** |
||||||
156 | * Temporary table based on parent. |
||||||
157 | 116 | */ |
|||||
158 | 64 | protected function createTemporary(AbstractTable $table): AbstractTable |
|||||
159 | { |
||||||
160 | //Temporary table is required to copy data over |
||||||
161 | 116 | $temporary = clone $table; |
|||||
162 | $temporary->setName( |
||||||
163 | 116 | 'spiral_temp_' . $table->getFullName() . '_' . \uniqid(), |
|||||
164 | ); |
||||||
165 | |||||||
166 | //We don't need any indexes in temporary table |
||||||
167 | foreach ($temporary->getIndexes() as $index) { |
||||||
168 | $temporary->dropIndex($index->getColumnsWithSort()); |
||||||
169 | 158 | } |
|||||
170 | |||||||
171 | 158 | $this->createTable($temporary); |
|||||
172 | |||||||
173 | 158 | return $temporary; |
|||||
174 | 158 | } |
|||||
175 | 158 | ||||||
176 | 158 | /** |
|||||
177 | * Rebuild is required when columns or foreign keys are altered. |
||||||
178 | 158 | */ |
|||||
179 | 158 | private function requiresRebuild(AbstractTable $table): bool |
|||||
180 | 158 | { |
|||||
181 | $comparator = $table->getComparator(); |
||||||
182 | |||||||
183 | 158 | $difference = [ |
|||||
184 | \count($comparator->addedColumns()), |
||||||
185 | \count($comparator->droppedColumns()), |
||||||
186 | \count($comparator->alteredColumns()), |
||||||
187 | |||||||
188 | \count($comparator->addedForeignKeys()), |
||||||
189 | \count($comparator->droppedForeignKeys()), |
||||||
190 | \count($comparator->alteredForeignKeys()), |
||||||
191 | ]; |
||||||
192 | |||||||
193 | return \array_sum($difference) !== 0; |
||||||
194 | } |
||||||
195 | |||||||
196 | /** |
||||||
197 | * Copy table data to another location. |
||||||
198 | 116 | * |
|||||
199 | * @see http://stackoverflow.com/questions/4007014/alter-column-in-sqlite |
||||||
200 | 116 | * |
|||||
201 | 116 | * @psalm-param non-empty-string $source |
|||||
202 | * @psalm-param non-empty-string $to |
||||||
203 | * |
||||||
204 | 116 | * @param array $mapping (destination => source) |
|||||
205 | 116 | * |
|||||
206 | * @throws HandlerException |
||||||
207 | 116 | */ |
|||||
208 | 116 | private function copyData(string $source, string $to, array $mapping): void |
|||||
209 | 116 | { |
|||||
210 | 116 | $sourceColumns = \array_keys($mapping); |
|||||
211 | 116 | $targetColumns = \array_values($mapping); |
|||||
212 | 116 | ||||||
213 | //Preparing mapping |
||||||
214 | $sourceColumns = \array_map([$this, 'identify'], $sourceColumns); |
||||||
215 | 116 | $targetColumns = \array_map([$this, 'identify'], $targetColumns); |
|||||
216 | 116 | ||||||
217 | $query = \sprintf( |
||||||
218 | 'INSERT INTO %s (%s) SELECT %s FROM %s', |
||||||
219 | $this->identify($to), |
||||||
220 | \implode(', ', $targetColumns), |
||||||
221 | 116 | \implode(', ', $sourceColumns), |
|||||
222 | $this->identify($source), |
||||||
223 | 116 | ); |
|||||
224 | 116 | ||||||
225 | 116 | $this->run($query); |
|||||
226 | 116 | } |
|||||
227 | |||||||
228 | /** |
||||||
229 | * Get mapping between new and initial columns. |
||||||
230 | 116 | */ |
|||||
231 | private function createMapping(AbstractTable $source, AbstractTable $target): array |
||||||
232 | { |
||||||
233 | $mapping = []; |
||||||
234 | foreach ($target->getColumns() as $name => $column) { |
||||||
235 | if ($source->hasColumn($name)) { |
||||||
236 | $mapping[$name] = $column->getName(); |
||||||
237 | } |
||||||
238 | } |
||||||
239 | |||||||
240 | return $mapping; |
||||||
241 | } |
||||||
242 | } |
||||||
243 |
The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g.
excluded_paths: ["lib/*"]
, you can move it to the dependency path list as follows:For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths