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": |
|
|
|
|
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": |
|
|
|
|
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
|
|
|
|
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.