Passed
Push — main ( f21efc...c5607e )
by Thierry
01:51
created

Database::createTable()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 4
c 1
b 0
f 0
nc 2
nop 1
dl 0
loc 7
rs 10
1
<?php
2
3
namespace Lagdo\DbAdmin\Driver\Sqlite\Db;
4
5
use Lagdo\DbAdmin\Driver\Entity\TableEntity;
6
use Lagdo\DbAdmin\Driver\Db\Database as AbstractDatabase;
7
8
class Database extends AbstractDatabase
9
{
10
    /**
11
     * Recreate a table
12
     *
13
     * @param TableEntity $tableAttrs
14
     * @param string $table
15
     *
16
     * @return bool
17
     */
18
    private function recreateTable(TableEntity $tableAttrs, string $table = '')
19
    {
20
        $alter = [];
21
        $originals = [];
22
        $indexes = [];
23
        foreach ($tableAttrs->fields as $field) {
24
            if ($field[1]) {
25
                $alter[] = (\is_string($field[1]) ? $field[1] : 'ADD ' . implode($field[1]));
26
                if ($field[0] != '') {
27
                    $originals[$field[0]] = $field[1][0];
28
                }
29
            }
30
        }
31
32
        if ($table != '') {
33
            if (empty($fields)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $fields seems to never exist and therefore empty should always be true.
Loading history...
34
                foreach ($this->driver->fields($table) as $key => $field) {
35
                    if (!empty($indexes)) {
36
                        $field->autoIncrement = 0;
37
                    }
38
                    $fields[] = $this->util->processField($field, $field);
39
                    $originals[$key] = $this->driver->escapeId($key);
40
                }
41
            }
42
            $primary_key = false;
43
            foreach ($fields as $field) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $fields does not seem to be defined for all execution paths leading up to this point.
Loading history...
44
                if ($field[6]) {
45
                    $primary_key = true;
46
                }
47
            }
48
            $drop_indexes = [];
49
            foreach ($indexes as $key => $val) {
50
                if ($val[2] == 'DROP') {
51
                    $drop_indexes[$val[1]] = true;
52
                    unset($indexes[$key]);
53
                }
54
            }
55
            foreach ($this->driver->indexes($table) as $key_name => $index) {
56
                $columns = [];
57
                foreach ($index->columns as $key => $column) {
58
                    if (!$originals[$column]) {
59
                        continue 2;
60
                    }
61
                    $columns[] = $originals[$column] . ($index->descs[$key] ? ' DESC' : '');
62
                }
63
                if (!$drop_indexes[$key_name]) {
64
                    if ($index->type != 'PRIMARY' || !$primary_key) {
65
                        $indexes[] = [$index->type, $key_name, $columns];
66
                    }
67
                }
68
            }
69
            foreach ($indexes as $key => $val) {
70
                if ($val[0] == 'PRIMARY') {
71
                    unset($indexes[$key]);
72
                    $foreign[] = '  PRIMARY KEY (' . implode(', ', $val[2]) . ')';
73
                }
74
            }
75
            foreach ($this->driver->foreignKeys($table) as $key_name => $foreignKey) {
76
                foreach ($foreignKey->source as $key => $column) {
77
                    if (!$originals[$column]) {
78
                        continue 2;
79
                    }
80
                    $foreignKey->source[$key] = $this->driver->unescapeId($originals[$column]);
81
                }
82
                if (!isset($foreign[" $key_name"])) {
83
                    $foreign[] = ' ' . $this->driver->formatForeignKey($foreignKey);
84
                }
85
            }
86
            $this->driver->execute('BEGIN');
87
        }
88
        foreach ($fields as $key => $field) {
89
            $fields[$key] = '  ' . implode($field);
90
        }
91
        $fields = array_merge($fields, array_filter($foreign));
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $foreign does not seem to be defined for all execution paths leading up to this point.
Loading history...
92
        $tempName = ($table == $name ? "adminer_$name" : $name);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $name seems to be never defined.
Loading history...
93
        if (!$this->driver->execute('CREATE TABLE ' . $this->driver->table($tempName) .
94
            " (\n" . implode(",\n", $fields) . "\n)")) {
95
            // implicit ROLLBACK to not overwrite $this->driver->error()
96
            return false;
97
        }
98
        if ($table != '') {
99
            if ($originals && !$this->driver->execute('INSERT INTO ' . $this->driver->table($tempName) .
100
                ' (' . implode(', ', $originals) . ') SELECT ' . implode(
101
                    ', ',
102
                    array_map(function ($key) {
103
                   return $this->driver->escapeId($key);
104
               }, array_keys($originals))
105
                ) . ' FROM ' . $this->driver->table($table))) {
106
                return false;
107
            }
108
            $triggers = [];
109
            foreach ($this->driver->triggers($table) as $trigger_name => $timing_event) {
110
                $trigger = $this->driver->trigger($trigger_name);
111
                $triggers[] = 'CREATE TRIGGER ' . $this->driver->escapeId($trigger_name) . ' ' .
112
                    implode(' ', $timing_event) . ' ON ' . $this->driver->table($name) . "\n$trigger[Statement]";
113
            }
114
            $autoIncrement = $autoIncrement ? 0 :
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $autoIncrement seems to be never defined.
Loading history...
115
                $this->connection->result('SELECT seq FROM sqlite_sequence WHERE name = ' .
116
                $this->driver->quote($table)); // if $autoIncrement is set then it will be updated later
0 ignored issues
show
Bug introduced by
The method quote() does not exist on Lagdo\DbAdmin\Driver\DriverInterface. Did you maybe mean quoteBinary()? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

116
                $this->driver->/** @scrutinizer ignore-call */ 
117
                               quote($table)); // if $autoIncrement is set then it will be updated later

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.

Loading history...
117
            // drop before creating indexes and triggers to allow using old names
118
            if (!$this->driver->execute('DROP TABLE ' . $this->driver->table($table)) ||
119
                ($table == $name && !$this->driver->execute('ALTER TABLE ' . $this->driver->table($tempName) .
120
                ' RENAME TO ' . $this->driver->table($name))) || !$this->alterIndexes($name, $indexes)
0 ignored issues
show
Bug introduced by
The call to Lagdo\DbAdmin\Driver\Sql...atabase::alterIndexes() has too few arguments starting with drop. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

120
                ' RENAME TO ' . $this->driver->table($name))) || !$this->/** @scrutinizer ignore-call */ alterIndexes($name, $indexes)

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
121
            ) {
122
                return false;
123
            }
124
            if ($autoIncrement) {
125
                $this->driver->execute('UPDATE sqlite_sequence SET seq = $autoIncrement WHERE name = ' . $this->driver->quote($name)); // ignores error
126
            }
127
            foreach ($triggers as $trigger) {
128
                if (!$this->driver->execute($trigger)) {
129
                    return false;
130
                }
131
            }
132
            $this->driver->execute('COMMIT');
133
        }
134
        return true;
135
    }
136
137
    /**
138
     * @param string $table
139
     * @param int $autoIncrement
140
     *
141
     * @return void
142
     */
143
    private function setAutoIncrement(string $table, int $autoIncrement)
144
    {
145
        if ($autoIncrement) {
146
            $this->driver->execute('BEGIN');
147
            $this->driver->execute("UPDATE sqlite_sequence SET seq = $autoIncrement WHERE name = " .
148
                $this->driver->quote($table)); // ignores error
149
            if (!$this->driver->affectedRows()) {
150
                $this->driver->execute('INSERT INTO sqlite_sequence (name, seq) VALUES (' .
151
                    $this->driver->quote($table) . ", $autoIncrement)");
152
            }
153
            $this->driver->execute('COMMIT');
154
        }
155
    }
156
157
    /**
158
     * @inheritDoc
159
     */
160
    public function createTable(TableEntity $tableAttrs)
161
    {
162
        if (!$this->recreateTable($tableAttrs)) {
163
            return false;
164
        }
165
        $this->setAutoIncrement($tableAttrs->name, $tableAttrs->autoIncrement);
166
        return true;
167
    }
168
169
    /**
170
     * @inheritDoc
171
     */
172
    public function alterTable(string $table, TableEntity $tableAttrs)
173
    {
174
        $use_all_fields = !empty($tableAttrs->foreign);
175
        foreach ($tableAttrs->fields as $field) {
176
            if ($field[0] != '' || !$field[1] || $field[2]) {
177
                $use_all_fields = true;
178
                break;
179
            }
180
        }
181
        if (!$use_all_fields) {
182
            $alter = [];
183
            foreach ($tableAttrs->fields as $field) {
184
                if ($field[1]) {
185
                    $alter[] = ($use_all_fields ? $field[1] : 'ADD ' . implode($field[1]));
186
                }
187
            }
188
            foreach ($alter as $val) {
189
                if (!$this->driver->execute('ALTER TABLE ' . $this->driver->table($table) . " $val")) {
190
                    return false;
191
                }
192
            }
193
            if ($table != $tableAttrs->name && !$this->driver->execute('ALTER TABLE ' .
194
                $this->driver->table($table) . ' RENAME TO ' . $this->driver->table($tableAttrs->name))) {
195
                return false;
196
            }
197
        } elseif (!$this->recreateTable($tableAttrs, $table)) {
198
            return false;
199
        }
200
        $this->setAutoIncrement($tableAttrs->name, $tableAttrs->autoIncrement);
201
        return true;
202
    }
203
204
    /**
205
     * @inheritDoc
206
     */
207
    public function alterIndexes(string $table, array $alter, array $drop)
208
    {
209
        foreach ($alter as $index) {
210
            if ($index->type == 'PRIMARY') {
211
                // return $this->recreateTable($table, $table, [], [], [], 0, $alter);
212
                // Do not alter primary keys, since it requires to recreate the table.
213
                return false;
214
            }
215
        }
216
        foreach (array_reverse($drop) as $index) {
217
            $this->driver->execute('DROP INDEX ' . $this->driver->escapeId($index->name));
218
        }
219
        foreach (array_reverse($alter) as $index) {
220
            $this->driver->execute($this->driver->sqlForCreateIndex($table,
221
                $index->type, $index->name, '(' . implode(', ', $index->columns) . ')'));
222
        }
223
        return true;
224
    }
225
226
    /**
227
     * @inheritDoc
228
     */
229
    public function tables()
230
    {
231
        return $this->driver->keyValues('SELECT name, type FROM sqlite_master ' .
232
            "WHERE type IN ('table', 'view') ORDER BY (name = 'sqlite_sequence'), name");
233
    }
234
235
    /**
236
     * @inheritDoc
237
     */
238
    public function countTables(array $databases)
239
    {
240
        $connection = $this->driver->createConnection(); // New connection
241
        $counts = [];
242
        $query = "SELECT count(*) FROM sqlite_master WHERE type IN ('table', 'view')";
243
        foreach ($databases as $database) {
244
            $counts[$database] = 0;
245
            $connection->open($database);
246
            $statement = $connection->query($query);
247
            if (is_object($statement) && ($row = $statement->fetchRow())) {
248
                $counts[$database] = intval($row[0]);
249
            }
250
        }
251
        return $counts;
252
    }
253
254
    /**
255
     * @inheritDoc
256
     */
257
    public function dropViews(array $views)
258
    {
259
        return $this->driver->applyQueries('DROP VIEW', $views);
260
    }
261
262
    /**
263
     * @inheritDoc
264
     */
265
    public function dropTables(array $tables)
266
    {
267
        return $this->driver->applyQueries('DROP TABLE', $tables);
268
    }
269
270
    /**
271
     * @inheritDoc
272
     */
273
    public function moveTables(array $tables, array $views, string $target)
274
    {
275
        return false;
276
    }
277
278
    /**
279
     * @inheritDoc
280
     */
281
    public function truncateTables(array $tables)
282
    {
283
        return $this->driver->applyQueries('DELETE FROM', $tables);
284
    }
285
}
286