Completed
Branch feature/pre-split (42159e)
by Anton
05:36
created

SQLiteHandler::requiresRebuild()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 19
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 10
nc 1
nop 1
dl 0
loc 19
rs 9.4285
c 0
b 0
f 0
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()) {
1 ignored issue
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

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.

Loading history...
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
}