Passed
Push — main ( 1918cf...7ee8f3 )
by Thierry
01:44
created

Table::makePrimaryIndex()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 19
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 5
eloc 13
nc 5
nop 2
dl 0
loc 19
rs 9.5222
c 2
b 0
f 0
1
<?php
2
3
namespace Lagdo\DbAdmin\Driver\Sqlite\Db;
4
5
use Lagdo\DbAdmin\Driver\Db\ConnectionInterface;
6
use Lagdo\DbAdmin\Driver\Entity\TableFieldEntity;
7
use Lagdo\DbAdmin\Driver\Entity\TableEntity;
8
use Lagdo\DbAdmin\Driver\Entity\IndexEntity;
9
use Lagdo\DbAdmin\Driver\Entity\ForeignKeyEntity;
10
11
use Lagdo\DbAdmin\Driver\Db\Table as AbstractTable;
12
13
use function str_replace;
14
use function strtolower;
15
use function preg_match;
16
use function preg_match_all;
17
use function preg_replace;
18
use function preg_quote;
19
20
class Table extends AbstractTable
21
{
22
    use TableTrait;
23
24
    /**
25
     * @inheritDoc
26
     */
27
    public function isView(TableEntity $tableStatus)
28
    {
29
        return $tableStatus->engine == 'view';
30
    }
31
32
    /**
33
     * @inheritDoc
34
     */
35
    public function supportForeignKeys(TableEntity $tableStatus)
36
    {
37
        return !$this->connection->result("SELECT sqlite_compileoption_used('OMIT_FOREIGN_KEY')");
38
    }
39
40
    /**
41
     * @param string $type
42
     *
43
     * @return string
44
     */
45
    private function rowType(string $type)
46
    {
47
        if (preg_match('~int~i', $type)) {
48
            return 'integer';
49
        }
50
        if (preg_match('~char|clob|text~i', $type)) {
51
            return 'text';
52
        }
53
        if (preg_match('~blob~i', $type)) {
54
            return 'blob';
55
        }
56
        if (preg_match('~real|floa|doub~i', $type)) {
57
            return 'real';
58
        }
59
        return 'numeric';
60
    }
61
62
    private function defaultvalue(array $row)
63
    {
64
        $default = $row["dflt_value"];
65
        if (preg_match("~'(.*)'~", $default, $match)) {
66
            return str_replace("''", "'", $match[1]);
67
        }
68
        if ($default == "NULL") {
69
            return null;
70
        }
71
        return $default;
72
    }
73
74
    /**
75
     * @param array $row
76
     *
77
     * @return TableFieldEntity
78
     */
79
    private function makeFieldEntity(array $row)
80
    {
81
        $field = new TableFieldEntity();
82
83
        $type = strtolower($row["type"]);
84
        $field->name = $row["name"];
85
        $field->type = $this->rowType($type);
86
        $field->fullType = $type;
87
        $field->default = $this->defaultvalue($row);
88
        $field->null = !$row["notnull"];
89
        $field->privileges = ["select" => 1, "insert" => 1, "update" => 1];
90
        $field->primary = $row["pk"];
91
        return $field;
92
    }
93
94
    /**
95
     * @param string $table
96
     *
97
     * @return array
98
     */
99
    private function tableFields(string $table)
100
    {
101
        $fields = [];
102
        $rows = $this->driver->rows('PRAGMA table_info(' . $this->driver->table($table) . ')');
103
        $primary = "";
104
        foreach ($rows as $row) {
105
            $name = $row["name"];
106
            $type = strtolower($row["type"]);
107
            $field = $this->makeFieldEntity($row);
108
            if ($row["pk"]) {
109
                if ($primary != "") {
110
                    $fields[$primary]->autoIncrement = false;
111
                } elseif (preg_match('~^integer$~i', $type)) {
112
                    $field->autoIncrement = true;
113
                }
114
                $primary = $name;
115
            }
116
            $fields[$name] = $field;
117
        }
118
        return $fields;
119
    }
120
121
    /**
122
     * @inheritDoc
123
     */
124
    public function fields(string $table)
125
    {
126
        $fields = $this->tableFields($table);
127
        $query = "SELECT sql FROM sqlite_master WHERE type IN ('table', 'view') AND name = " . $this->driver->quote($table);
128
        $result = $this->connection->result($query);
129
        $pattern = '~(("[^"]*+")+|[a-z0-9_]+)\s+text\s+COLLATE\s+(\'[^\']+\'|\S+)~i';
130
        preg_match_all($pattern, $result, $matches, PREG_SET_ORDER);
131
        foreach ($matches as $match) {
132
            $name = str_replace('""', '"', preg_replace('~^"|"$~', '', $match[1]));
133
            if (isset($fields[$name])) {
134
                $fields[$name]->collation = trim($match[3], "'");
135
            }
136
        }
137
        return $fields;
138
    }
139
140
    /**
141
     * @param array $row
142
     * @param array $results
143
     * @param string $table
144
     * @param ConnectionInterface $connection
145
     *
146
     * @return IndexEntity
147
     */
148
    private function makeIndexEntity(array $row, array $results, string $table, ConnectionInterface $connection)
149
    {
150
        $index = new IndexEntity();
151
152
        $name = $row["name"];
153
        $index->type = $row["unique"] ? "UNIQUE" : "INDEX";
154
        $index->lengths = [];
155
        $index->descs = [];
156
        $columns = $this->driver->rows("PRAGMA index_info(" .
157
            $this->driver->escapeId($name) . ")", $connection);
158
        foreach ($columns as $column) {
159
            $index->columns[] = $column["name"];
160
            $index->descs[] = null;
161
        }
162
        if (preg_match('~^CREATE( UNIQUE)? INDEX ' . preg_quote($this->driver->escapeId($name) . ' ON ' .
163
            $this->driver->escapeId($table), '~') . ' \((.*)\)$~i', $results[$name], $regs)) {
164
            preg_match_all('/("[^"]*+")+( DESC)?/', $regs[2], $matches);
165
            foreach ($matches[2] as $key => $val) {
166
                if ($val) {
167
                    $index->descs[$key] = '1';
168
                }
169
            }
170
        }
171
        return $index;
172
    }
173
174
    /**
175
     * @param string $table
176
     * @param ConnectionInterface $connection
177
     *
178
     * @return IndexEntity|null
179
     */
180
    private function queryPrimaryIndex(string $table, ConnectionInterface $connection)
181
    {
182
        $primaryIndex = null;
183
        $query = "SELECT sql FROM sqlite_master WHERE type = 'table' AND name = " . $this->driver->quote($table);
184
        $result = $connection->result($query);
185
        if (preg_match('~\bPRIMARY\s+KEY\s*\((([^)"]+|"[^"]*"|`[^`]*`)++)~i', $result, $match)) {
186
            $primaryIndex = new IndexEntity();
187
            $primaryIndex->type = "PRIMARY";
188
            preg_match_all('~((("[^"]*+")+|(?:`[^`]*+`)+)|(\S+))(\s+(ASC|DESC))?(,\s*|$)~i',
189
                $match[1], $matches, PREG_SET_ORDER);
190
            foreach ($matches as $match) {
191
                $primaryIndex->columns[] = $this->driver->unescapeId($match[2]) . $match[4];
192
                $primaryIndex->descs[] = (preg_match('~DESC~i', $match[5]) ? '1' : null);
193
            }
194
        }
195
        return $primaryIndex;
196
    }
197
198
    /**
199
     * @param string $table
200
     * @param ConnectionInterface $connection
201
     *
202
     * @return IndexEntity
203
     */
204
    private function makePrimaryIndex(string $table, ConnectionInterface $connection)
205
    {
206
        $primaryIndex = $this->queryPrimaryIndex($table, $connection);
207
        if ($primaryIndex !== null) {
208
            return $primaryIndex;
209
        }
210
        foreach ($this->fields($table) as $name => $field) {
211
            if (!$field->primary) {
212
                continue;
213
            }
214
            if ($primaryIndex === null) {
215
                $primaryIndex = new IndexEntity();
216
            }
217
            $primaryIndex->type = "PRIMARY";
218
            $primaryIndex->columns = [$name];
219
            $primaryIndex->lengths = [];
220
            $primaryIndex->descs = [null];
221
        }
222
        return $primaryIndex;
223
    }
224
225
    /**
226
     * @inheritDoc
227
     */
228
    public function indexes(string $table, ConnectionInterface $connection = null)
229
    {
230
        if (!$connection) {
231
            $connection = $this->connection;
232
        }
233
        $primaryIndex = $this->makePrimaryIndex($table, $connection);
234
        if ($primaryIndex === null) {
235
            return [];
236
        }
237
238
        $indexes = ['' => $primaryIndex];
239
        $query = "SELECT name, sql FROM sqlite_master WHERE type = 'index' AND tbl_name = " . $this->driver->quote($table);
240
        $results = $this->driver->keyValues($query, $connection);
241
        $rows = $this->driver->rows("PRAGMA index_list(" . $this->driver->table($table) . ")", $connection);
242
        foreach ($rows as $row) {
243
            $index = $this->makeIndexEntity($row, $results, $table, $connection);
244
            $name = $row["name"];
245
            if ($index->type === 'UNIQUE' && $index->columns == $primaryIndex->columns &&
246
                $index->descs == $primaryIndex->descs && preg_match("~^sqlite_~", $name)) {
247
                $indexes[$name] = $index;
248
            }
249
        }
250
251
        return $indexes;
252
    }
253
254
    /**
255
     * @inheritDoc
256
     */
257
    public function foreignKeys(string $table)
258
    {
259
        $foreignKeys = [];
260
        foreach ($this->driver->rows("PRAGMA foreign_key_list(" . $this->driver->table($table) . ")") as $row) {
261
            $name = $row["id"];
262
            if (!isset($foreignKeys[$name])) {
263
                $foreignKeys[$name] = new ForeignKeyEntity();
264
            }
265
            //! idf_unescape in SQLite2
266
            $foreignKeys[$name]->source[] = $row["from"];
267
            $foreignKeys[$name]->target[] = $row["to"];
268
        }
269
        return $foreignKeys;
270
    }
271
}
272