Completed
Push — master ( 2a510c...5bfa5e )
by Richard
03:56
created

IndexDB::to_graph_index()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 12
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 12
ccs 9
cts 9
cp 1
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 10
nc 1
nop 0
crap 1
1
<?php
2
/******************************************************************************
3
 * An implementation of dicto (scg.unibe.ch/dicto) in and for PHP.
4
 *
5
 * Copyright (c) 2016, 2015 Richard Klees <[email protected]>
6
 *
7
 * This software is licensed under The MIT License. You should have received
8
 * a copy of the license along with the code.
9
 */
10
11
namespace Lechimp\Dicto\DB;
12
13
use Lechimp\Dicto\Graph;
14
use Lechimp\Dicto\Indexer\Insert;
15
use Doctrine\DBAL\Schema;
16
use Doctrine\DBAL\Schema\Synchronizer\SingleDatabaseSynchronizer;
17
use Doctrine\DBAL\Statement;
18
19
class IndexDB extends DB implements Insert {
20
    protected $nodes_per_insert = 200;
21
22
    /**
23
     * @var array
24
     */
25
    protected $tables =
26
        [ "files" => ["_file",
27
            ["id", "path", "source"]]
28
        , "namespaces" => ["_namespace",
29
            ["id", "name"]]
30
        , "classes" => ["_class",
31
            ["id", "name", "file_id", "start_line", "end_line", "namespace_id"]]
32
        , "interfaces" => ["_interface",
33
            ["id", "name", "file_id", "start_line", "end_line", "namespace_id"]]
34
        , "traits" => ["_trait",
35
            ["id", "name", "file_id", "start_line", "end_line", "namespace_id"]]
36
        , "methods" => ["_method",
37
            ["id", "name", "class_id", "file_id", "start_line", "end_line"]]
38
        , "functions" => ["_function",
39
            ["id", "name", "file_id", "start_line", "end_line", "namespace_id"]]
40
        , "globals" => ["_global",
41
            ["id", "name"]]
42
        , "language_constructs" => ["_language_construct",
43
            ["id", "name"]]
44
        , "method_references" => ["_method_reference",
45
            ["id", "name", "file_id", "line", "column"]]
46
        , "function_references" => ["_function_reference",
47
            ["id", "name", "file_id", "line", "column"]]
48
        , "relations" => ["_relation",
49
            ["left_id", "relation", "right_id", "file_id", "line"]]
50
        ];
51
52
    /**
53
     * Lists of fields that contain an id.
54
     *
55
     * @var array<string,int>
56
     */
57
    protected $id_fields =
58
        [ "file_id" => 0
59
        , "namespace_id" => 0
60
        , "class_id" => 0
61
        , "left_id" => 0
62
        , "right_id" => 0
63
        ];
64
65
    /**
66
     * List of all fields that contain an integer.
67
     *
68
     * @var array<string,0>
69
     */
70
    protected $int_fields =
71
        [ "start_line" => 0
72
        , "end_line" => 0
73
        , "line" => 0
74
        , "column" => 0
75
        ];
76
77
    /**
78
     * @var array[]
79
     */
80
    protected $caches = [];
81
82
    /**
83
     * @var integer
84
     */
85
    protected $id_counter = 0;
86
87 2
    public function __construct($connection) {
88 2
        parent::__construct($connection);
89 2
        foreach ($this->tables as $table => $_) {
90 2
            $this->caches[$table] = [];
91 2
        }
92 2
    }
93
94
    /**
95
     * @inheritdocs
96
     */
97 1
    public function _file($path, $source) {
98 1
        return $this->append_and_maybe_flush("files",
99 1
            [null, $this->esc_str($path), $this->esc_str($source)]);
100
    }
101
102
    /**
103
     * @inheritdocs
104
     */
105 1
    public function _namespace($name) {
106 1
        return $this->append_and_maybe_flush("namespaces",
107 1
            [null, $this->esc_str($name)]);
108
    }
109
110
    /**
111
     * @inheritdocs
112
     */
113 1
    public function _class($name, $file, $start_line, $end_line, $namespace = null) {
114 1
        return $this->append_and_maybe_flush("classes",
115 1
            [null, $this->esc_str($name), $file, $start_line, $end_line, $this->esc_maybe_null($namespace)]);
116
    }
117
118
    /**
119
     * @inheritdocs
120
     */
121
    public function _interface($name, $file, $start_line, $end_line, $namespace = null) {
122
        return $this->append_and_maybe_flush("interfaces",
123
            [null, $this->esc_str($name), $file, $start_line, $end_line, $this->esc_maybe_null($namespace)]);
124
    }
125
126
    /**
127
     * @inheritdocs
128
     */
129
    public function _trait($name, $file, $start_line, $end_line, $namespace = null) {
130
        return $this->append_and_maybe_flush("traits",
131
            [null, $this->esc_str($name), $file, $start_line, $end_line, $this->esc_maybe_null($namespace)]);
132
    }
133
134
    /**
135
     * @inheritdocs
136
     */
137 1
    public function _method($name, $class, $file, $start_line, $end_line) {
138 1
        return $this->append_and_maybe_flush("methods",
139 1
            [null, $this->esc_str($name), $class, $file, $start_line, $end_line]);
140
    }
141
142
    /**
143
     * @inheritdocs
144
     */
145 1
    public function _function($name, $file, $start_line, $end_line, $namespace = null) {
146 1
        return $this->append_and_maybe_flush("functions",
147 1
            [null, $this->esc_str($name), $file, $start_line, $end_line, $this->esc_maybe_null($namespace)]);
148
    }
149
150
    /**
151
     * @inheritdocs
152
     */
153 1
    public function _global($name) {
154 1
        return $this->append_and_maybe_flush("globals",
155 1
            [null, $this->esc_str($name)]);
156
    }
157
158
    /**
159
     * @inheritdocs
160
     */
161 1
    public function _language_construct($name) {
162 1
        return $this->append_and_maybe_flush("language_constructs",
163 1
            [null, $this->esc_str($name)]);
164
    }
165
166
    /**
167
     * @inheritdocs
168
     */
169 1
    public function _method_reference($name, $file, $line, $column) {
170 1
        return $this->append_and_maybe_flush("method_references",
171 1
            [null, $this->esc_str($name), $file, $line, $column]);
172
    }
173
174
    /**
175
     * @inheritdocs
176
     */
177 1
    public function _function_reference($name, $file, $line, $column) {
178 1
        return $this->append_and_maybe_flush("function_references",
179 1
            [null, $this->esc_str($name), $file, $line, $column]);
180
    }
181
182
    /**
183
     * @inheritdocs
184
     */
185 1
    public function _relation($left_entity, $relation, $right_entity, $file, $line) {
186 1
        $this->append_and_maybe_flush("relations",
187 1
            [$left_entity, $this->esc_str($relation), $right_entity, $file, $line]);
188 1
    }
189
190 1
    protected function insert_cache($table) {
191 1
        assert('array_key_exists($table, $this->tables)');
192 1
        $fields = $this->tables[$table][1];
193 1
        $which = &$this->caches[$table];
194 1
        if (count($which) == 0) {
195 1
            return;
196
        }
197 1
        $stmt = "INSERT INTO $table (".implode(", ", $fields).") VALUES\n";
198 1
        $values = [];
199 1
        foreach ($which as $v) {
200 1
            $values[] = "(".implode(", ", $v).")";
201 1
        }
202 1
        $stmt .= implode(",\n", $values).";";
203 1
        $this->connection->exec($stmt);
204 1
        $which = [];
205 1
    }
206
207 1
    protected function append_and_maybe_flush($table, $values) {
208 1
        if ($values[0] === null) {
209 1
            $id = $this->id_counter++;
210 1
            $values[0] = $id;
211 1
        }
212
        else {
213 1
            $id = null;
214
        }
215
216 1
        $which = &$this->caches[$table];
217 1
        $which[] = $values;
218 1
        if (count($which) > $this->nodes_per_insert) {
219
            $this->insert_cache($table);
220
        }
221
222 1
        return $id;
223
    }
224 1
    protected function esc_str($str) {
225 1
        assert('is_string($str)');
226 1
        return '"'.str_replace('"', '""', $str).'"';
227
    }
228
229 1
    protected function esc_maybe_null($val) {
230 1
        assert('!is_string($val)');
231 1
        if ($val === null) {
232 1
            return "NULL";
233
        }
234 1
        return $val;
235
    }
236
237
    /**
238
     * Write everything that currently is in cache to the database.
239
     *
240
     * @return null
241
     */
242 1
    public function write_cached_inserts() {
243 1
        foreach ($this->tables as $table => $_) {
244 1
            $this->insert_cache($table);
245 1
        }
246 1
    }
247
248
    /**
249
     * Read the index from the database.
250
     *
251
     * @return  Graph\IndexDB   $index
252
     */
253 1
    public function to_graph_index() {
254 1
        $index = $this->build_graph_index_db();
255
        $reader = new IndexDBReader
256 1
            ( $this->tables
257 1
            , $this->id_fields
258 1
            , $this->int_fields
259
            , function () { return $this->builder(); }
260 1
            , $index
261 1
            );
262 1
        $reader->run();
263 1
        return $index;
264
    }
265
266
    protected function build_graph_index_db() {
267
        return new Graph\IndexDB();
268
    }
269
270
    // INIT DATABASE
271
272 1
    public function init_table($name, Schema\Schema $schema, Schema\Table $file_table = null, Schema\Table $namespace_table = null) {
273 1
        assert('array_key_exists($name, $this->tables)');
274 1
        $table = $schema->createTable($name);
275 1
        foreach ($this->tables[$name][1] as $field) {
276
            switch ($field) {
277 1
                case "id":
278 1
                case "file_id":
279 1
                case "class_id":
280 1
                case "left_id":
281 1
                case "right_id":
282 1
                case "start_line":
283 1
                case "end_line":
284 1
                case "line":
285 1 View Code Duplication
                case "column":
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
286 1
                    $table->addColumn
287 1
                        ($field, "integer"
288 1
                        , ["notnull" => true, "unsigned" => true]
289 1
                        );
290 1
                    break;
291 1 View Code Duplication
                case "namespace_id":
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
292 1
                    $table->addColumn
293 1
                        ($field, "integer"
294 1
                        , ["notnull" => false, "unsigned" => true]
295 1
                        );
296 1
                   break;
297 1
                case "path":
298 1
                case "source":
299 1
                case "name":
300 1
                case "relation":
301 1
                    $table->addColumn
302 1
                        ($field, "string"
303 1
                        , ["notnull" => true]
304 1
                        );
305 1
                    break;
306
                default:
307
                    throw new \LogicException("Unknown field '$field'");
308
            }
309 1
            if ($field == "id") {
310 1
                $table->setPrimaryKey(["id"]);
311 1
            }
312 1
            if ($field == "file_id") {
313 1
                if ($file_table instanceof Schema\Table) {
314 1
                    $table->addForeignKeyConstraint
315 1
                        ( $file_table
316 1
                        , array("file_id")
317 1
                        , array("id")
318 1
                        );
319 1
                }
320
                else {
321
                    throw new \LogicException(
322
                        "Expected \$file_table to be a schema when file_id is used.");
323
                }
324 1
            }
325 1
            if ($field == "namespace_id") {
326 1
                if ($namespace_table instanceof Schema\Table) {
327 1
                    $table->addForeignKeyConstraint
328 1
                        ( $namespace_table
329 1
                        , array("namespace_id")
330 1
                        , array("id")
331 1
                        );
332 1
                }
333
                else {
334
                    throw new \LogicException(
335
                        "Expected \$namespace_table to be a schema when namespace_id is used.");
336
                }
337 1
            }
338 1
        }
339 1
        return $table;
340
    }
341
342 1
    public function init_database_schema() {
343 1
        $schema = new Schema\Schema();
344
345 1
        $file_table = $this->init_table("files", $schema);
346 1
        $namespace_table = $this->init_table("namespaces", $schema);
347 1
        $this->init_table("classes", $schema, $file_table, $namespace_table);
348 1
        $this->init_table("interfaces", $schema, $file_table, $namespace_table);
349 1
        $this->init_table("traits", $schema, $file_table, $namespace_table);
350 1
        $this->init_table("methods", $schema, $file_table);
351 1
        $this->init_table("functions", $schema, $file_table, $namespace_table);
352 1
        $this->init_table("globals", $schema);
353 1
        $this->init_table("language_constructs", $schema);
354 1
        $this->init_table("method_references", $schema, $file_table);
355 1
        $this->init_table("function_references", $schema, $file_table);
356 1
        $relation_table = $this->init_table("relations", $schema, $file_table);
357 1
        $relation_table->setPrimaryKey(["left_id", "relation", "right_id"]);
358
359 1
        $sync = new SingleDatabaseSynchronizer($this->connection);
360 1
        $sync->createSchema($schema);
361 1
    }
362
}
363