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\Report; |
12
|
|
|
|
13
|
|
|
use Lechimp\Dicto\Analysis\Listener; |
14
|
|
|
use Lechimp\Dicto\Analysis\Violation; |
15
|
|
|
use Lechimp\Dicto\DB\DB; |
16
|
|
|
use Lechimp\Dicto\Rules\Ruleset; |
17
|
|
|
use Lechimp\Dicto\Rules\Rule; |
18
|
|
|
use Lechimp\Dicto\Variables\Variable; |
19
|
|
|
use Doctrine\DBAL\Schema; |
20
|
|
|
use Doctrine\DBAL\Schema\Synchronizer\SingleDatabaseSynchronizer; |
21
|
|
|
|
22
|
|
|
class ResultDB extends DB implements Listener { |
23
|
|
|
/** |
24
|
|
|
* @var int|null |
25
|
|
|
*/ |
26
|
|
|
private $current_run_id = null; |
27
|
|
|
|
28
|
|
|
/** |
29
|
|
|
* @var int|null |
30
|
|
|
*/ |
31
|
|
|
private $current_rule_id = null; |
32
|
|
|
|
33
|
|
|
// Analysis\Listener implementation |
34
|
|
|
|
35
|
|
|
/** |
36
|
|
|
* Announce to start a new run of the analysis now. |
37
|
|
|
* |
38
|
|
|
* @param string $commit_hash |
39
|
|
|
* @return null |
40
|
|
|
*/ |
41
|
|
|
public function begin_run($commit_hash) { |
42
|
|
|
assert('is_string($commit_hash)'); |
43
|
|
|
$this->builder() |
44
|
|
|
->insert("runs") |
45
|
|
|
->values(array |
46
|
|
|
( "commit_hash" => "?" |
47
|
|
|
)) |
48
|
|
|
->setParameter(0, $commit_hash) |
49
|
|
|
->execute(); |
50
|
|
|
$this->current_run_id = (int)$this->connection->lastInsertId(); |
51
|
|
|
} |
52
|
|
|
|
53
|
|
|
/** |
54
|
|
|
* @inheritdoc |
55
|
|
|
*/ |
56
|
|
|
public function end_run() { |
57
|
|
|
} |
58
|
|
|
|
59
|
|
|
/** |
60
|
|
|
* @inheritdoc |
61
|
|
|
*/ |
62
|
|
|
public function begin_ruleset(Ruleset $rule) { |
63
|
|
|
// Nothing to do here... |
64
|
|
|
} |
65
|
|
|
|
66
|
|
|
/** |
67
|
|
|
* @inheritdoc |
68
|
|
|
*/ |
69
|
|
|
public function end_ruleset() { |
70
|
|
|
// Nothing to do here... |
71
|
|
|
} |
72
|
|
|
|
73
|
|
|
/** |
74
|
|
|
* @inheritdoc |
75
|
|
|
*/ |
76
|
|
|
public function begin_rule(Rule $rule) { |
77
|
|
|
assert('$this->current_run_id !== null'); |
78
|
|
|
$rule_id = $this->rule_id($rule); |
79
|
|
View Code Duplication |
if ($rule_id === null) { |
|
|
|
|
80
|
|
|
$this->builder() |
81
|
|
|
->insert("rules") |
82
|
|
|
->values(array |
83
|
|
|
( "rule" => "?" |
84
|
|
|
, "first_seen" => "?" |
85
|
|
|
, "last_seen" => "?" |
86
|
|
|
, "explanation" => "?" |
87
|
|
|
)) |
88
|
|
|
->setParameter(0, $rule->pprint()) |
89
|
|
|
->setParameter(1, $this->current_run_id) |
90
|
|
|
->setParameter(2, $this->current_run_id) |
91
|
|
|
->setParameter(3, $rule->explanation()) |
92
|
|
|
->execute(); |
93
|
|
|
$rule_id = (int)$this->connection->lastInsertId(); |
94
|
|
|
} |
95
|
|
|
else { |
96
|
|
|
$this->builder() |
97
|
|
|
->update("rules") |
98
|
|
|
->set("last_seen", "?") |
99
|
|
|
->set("explanation", "?") |
100
|
|
|
->where("id = ?") |
101
|
|
|
->setParameter(0, $this->current_run_id) |
102
|
|
|
->setParameter(1, $rule->explanation()) |
103
|
|
|
->setParameter(2, $rule_id) |
104
|
|
|
->execute(); |
105
|
|
|
} |
106
|
|
|
foreach ($rule->variables() as $variable) { |
107
|
|
|
$this->upsert_variable($variable); |
108
|
|
|
} |
109
|
|
|
$this->current_rule_id = $rule_id; |
110
|
|
|
} |
111
|
|
|
|
112
|
|
|
/** |
113
|
|
|
* @inheritdoc |
114
|
|
|
*/ |
115
|
|
|
public function end_rule() { |
116
|
|
|
$this->current_rule_id = null; |
117
|
|
|
} |
118
|
|
|
|
119
|
|
|
/** |
120
|
|
|
* @inheritdoc |
121
|
|
|
*/ |
122
|
|
|
public function report_violation(Violation $violation) { |
123
|
|
|
assert('$this->current_run_id !== null'); |
124
|
|
|
assert('$this->current_rule_id !== null'); |
125
|
|
|
$violation_id = $this->violation_id($violation); |
126
|
|
View Code Duplication |
if ($violation_id === null) { |
|
|
|
|
127
|
|
|
$this->builder() |
128
|
|
|
->insert("violations") |
129
|
|
|
->values(array |
130
|
|
|
( "rule_id" => "?" |
131
|
|
|
, "file" => "?" |
132
|
|
|
, "line" => "?" |
133
|
|
|
, "first_seen" => "?" |
134
|
|
|
, "last_seen" => "?" |
135
|
|
|
)) |
136
|
|
|
->setParameter(0, $this->current_rule_id) |
137
|
|
|
->setParameter(1, $violation->filename()) |
138
|
|
|
->setParameter(2, $violation->line()) |
139
|
|
|
->setParameter(3, $this->current_run_id) |
140
|
|
|
->setParameter(4, $this->current_run_id) |
141
|
|
|
->execute(); |
142
|
|
|
$violation_id = (int)$this->connection->lastInsertId(); |
143
|
|
|
} |
144
|
|
|
else { |
145
|
|
|
$this->builder() |
146
|
|
|
->update("violations") |
147
|
|
|
->set("last_seen", "?") |
148
|
|
|
->where("id = ?") |
149
|
|
|
->setParameter(0, $this->current_run_id) |
150
|
|
|
->setParameter(1, $violation_id) |
151
|
|
|
->execute(); |
152
|
|
|
} |
153
|
|
|
|
154
|
|
|
$this->builder() |
155
|
|
|
->insert("violation_locations") |
156
|
|
|
->values(array |
157
|
|
|
( "violation_id" => "?" |
158
|
|
|
, "run_id" => "?" |
159
|
|
|
, "line_no" => "?" |
160
|
|
|
)) |
161
|
|
|
->setParameter(0, $violation_id) |
162
|
|
|
->setParameter(1, $this->current_run_id) |
163
|
|
|
->setParameter(2, $violation->line_no()) |
164
|
|
|
->execute(); |
165
|
|
|
} |
166
|
|
|
|
167
|
|
|
// Helpers |
168
|
|
|
|
169
|
|
|
|
170
|
|
|
/** |
171
|
|
|
* @param Rule $rule |
172
|
|
|
* @return int|null |
173
|
|
|
*/ |
174
|
|
|
protected function rule_id(Rule $rule) { |
175
|
|
|
$res = $this->builder() |
176
|
|
|
->select("id") |
177
|
|
|
->from("rules") |
178
|
|
|
->where("rule = ?") |
179
|
|
|
->setParameter(0, $rule->pprint()) |
180
|
|
|
->execute() |
181
|
|
|
->fetch(); |
182
|
|
|
if ($res) { |
183
|
|
|
return (int)$res["id"]; |
184
|
|
|
} |
185
|
|
|
else { |
186
|
|
|
return null; |
187
|
|
|
} |
188
|
|
|
} |
189
|
|
|
|
190
|
|
|
/** |
191
|
|
|
* @param Variable $var |
192
|
|
|
* @return int|null |
193
|
|
|
*/ |
194
|
|
View Code Duplication |
protected function variable_id(Variable $var) { |
|
|
|
|
195
|
|
|
$res = $this->builder() |
196
|
|
|
->select("id") |
197
|
|
|
->from("variables") |
198
|
|
|
->where($this->builder()->expr()->andX |
199
|
|
|
( "name = ?" |
200
|
|
|
, "meaning = ?" |
201
|
|
|
)) |
202
|
|
|
->setParameter(0, $var->name()) |
203
|
|
|
->setParameter(1, $var->meaning()) |
204
|
|
|
->execute() |
205
|
|
|
->fetch(); |
206
|
|
|
if ($res) { |
207
|
|
|
return (int)$res["id"]; |
208
|
|
|
} |
209
|
|
|
else { |
210
|
|
|
return null; |
211
|
|
|
} |
212
|
|
|
} |
213
|
|
|
|
214
|
|
|
protected function upsert_variable(Variable $var) { |
215
|
|
|
$var_id = $this->variable_id($var); |
216
|
|
|
if ($var_id === null) { |
217
|
|
|
$var_id = $this->insert_variable($var); |
218
|
|
|
} |
219
|
|
|
else { |
220
|
|
|
$this->update_variable($var_id); |
221
|
|
|
} |
222
|
|
|
return $var_id; |
223
|
|
|
} |
224
|
|
|
|
225
|
|
|
protected function insert_variable(Variable $var) { |
226
|
|
|
assert('$this->current_run_id !== null'); |
227
|
|
|
$this->builder() |
228
|
|
|
->insert("variables") |
229
|
|
|
->values(array |
230
|
|
|
( "name" => "?" |
231
|
|
|
, "meaning" => "?" |
232
|
|
|
, "first_seen" => "?" |
233
|
|
|
, "last_seen" => "?" |
234
|
|
|
)) |
235
|
|
|
->setParameter(0, $var->name()) |
236
|
|
|
->setParameter(1, $var->meaning()) |
237
|
|
|
->setParameter(2, $this->current_run_id) |
238
|
|
|
->setParameter(3, $this->current_run_id) |
239
|
|
|
->execute(); |
240
|
|
|
return (int)$this->connection->lastInsertId(); |
241
|
|
|
} |
242
|
|
|
|
243
|
|
|
protected function update_variable($var_id) { |
244
|
|
|
assert('is_integer($var_id)'); |
245
|
|
|
assert('$this->current_run_id !== null'); |
246
|
|
|
$this->builder() |
247
|
|
|
->update("variables") |
248
|
|
|
->set("last_seen", "?") |
249
|
|
|
->where("id = ?") |
250
|
|
|
->setParameter(0, $this->current_run_id) |
251
|
|
|
->setParameter(1, $var_id) |
252
|
|
|
->execute(); |
253
|
|
|
} |
254
|
|
|
|
255
|
|
|
/** |
256
|
|
|
* @param Violation $violation |
257
|
|
|
* @return int|null |
258
|
|
|
*/ |
259
|
|
View Code Duplication |
protected function violation_id(Violation $violation) { |
|
|
|
|
260
|
|
|
$res = $this->builder() |
261
|
|
|
->select("id") |
262
|
|
|
->from("violations") |
263
|
|
|
->where($this->builder()->expr()->andX |
264
|
|
|
( "rule_id = ?" |
265
|
|
|
, "file = ?" |
266
|
|
|
, "line = ?" |
267
|
|
|
)) |
268
|
|
|
->setParameter(0, $this->current_rule_id) |
269
|
|
|
->setParameter(1, $violation->filename()) |
270
|
|
|
->setParameter(2, $violation->line()) |
271
|
|
|
->execute() |
272
|
|
|
->fetch(); |
273
|
|
|
if ($res) { |
274
|
|
|
return (int)$res["id"]; |
275
|
|
|
} |
276
|
|
|
else { |
277
|
|
|
return null; |
278
|
|
|
} |
279
|
|
|
} |
280
|
|
|
|
281
|
|
|
// Creation of database. |
282
|
|
|
|
283
|
|
|
protected function init_run_table(Schema\Schema $schema) { |
284
|
|
|
$run_table = $schema->createTable("runs"); |
285
|
|
|
$run_table->addColumn |
286
|
|
|
("id", "integer" |
287
|
|
|
, array("notnull" => true, "unsigned" => true, "autoincrement" => true) |
288
|
|
|
); |
289
|
|
|
$run_table->addColumn |
290
|
|
|
( "commit_hash", "string" |
291
|
|
|
, array("notnull" => true) |
292
|
|
|
); |
293
|
|
|
// TODO: maybe add time |
294
|
|
|
// TODO: do we need some other meta information per run of the analysis? |
295
|
|
|
// TODO: Might be a good idea to store config and rules file here |
296
|
|
|
$run_table->setPrimaryKey(array("id")); |
297
|
|
|
|
298
|
|
|
return $run_table; |
299
|
|
|
} |
300
|
|
|
|
301
|
|
|
protected function init_rule_table(Schema\Schema $schema, Schema\Table $run_table) { |
302
|
|
|
$rule_table = $schema->createTable("rules"); |
303
|
|
|
$rule_table->addColumn |
304
|
|
|
( "id", "integer" |
305
|
|
|
, array("notnull" => true, "unsigned" => true, "autoincrement" => true) |
306
|
|
|
); |
307
|
|
|
$rule_table->addColumn |
308
|
|
|
( "rule", "string" |
309
|
|
|
, array("notnull" => true) |
310
|
|
|
); |
311
|
|
|
|
312
|
|
|
$rule_table->addColumn |
313
|
|
|
( "explanation", "string" |
314
|
|
|
, array("notnull" => false) |
315
|
|
|
); |
316
|
|
|
$rule_table->addColumn |
317
|
|
|
( "first_seen", "integer" |
318
|
|
|
, array("notnull" => true) |
319
|
|
|
); |
320
|
|
|
$rule_table->addColumn |
321
|
|
|
( "last_seen", "integer" |
322
|
|
|
, array("notnull" => true) |
323
|
|
|
); |
324
|
|
|
$rule_table->setPrimaryKey(array("id")); |
325
|
|
|
$rule_table->addUniqueIndex(array("rule")); |
326
|
|
|
$rule_table->addForeignKeyConstraint |
327
|
|
|
( $run_table |
328
|
|
|
, array("first_seen") |
329
|
|
|
, array("id") |
330
|
|
|
); |
331
|
|
|
$rule_table->addForeignKeyConstraint |
332
|
|
|
( $run_table |
333
|
|
|
, array("last_seen") |
334
|
|
|
, array("id") |
335
|
|
|
); |
336
|
|
|
|
337
|
|
|
return $rule_table; |
338
|
|
|
} |
339
|
|
|
|
340
|
|
|
public function init_variable_table(Schema\Schema $schema) { |
341
|
|
|
$variable_table = $schema->createTable("variables"); |
342
|
|
|
$variable_table->addColumn |
343
|
|
|
( "id", "integer" |
344
|
|
|
, array("notnull" => true) |
345
|
|
|
); |
346
|
|
|
$variable_table->addColumn |
347
|
|
|
( "name", "string" |
348
|
|
|
, array("notnull" => true) |
349
|
|
|
); |
350
|
|
|
$variable_table->addColumn |
351
|
|
|
( "meaning", "string" |
352
|
|
|
, array("notnull" => true) |
353
|
|
|
); |
354
|
|
|
// TODO: Some field for explanation is missing here. |
355
|
|
|
$variable_table->addColumn |
356
|
|
|
( "first_seen", "integer" |
357
|
|
|
, array("notnull" => true) |
358
|
|
|
); |
359
|
|
|
$variable_table->addColumn |
360
|
|
|
( "last_seen", "integer" |
361
|
|
|
, array("notnull" => true) |
362
|
|
|
); |
363
|
|
|
$variable_table->setPrimaryKey(array("id")); |
364
|
|
|
|
365
|
|
|
return $variable_table; |
366
|
|
|
} |
367
|
|
|
|
368
|
|
|
protected function init_violation_table(Schema\Schema $schema, Schema\Table $run_table, Schema\Table $rule_table) { |
369
|
|
|
$violation_table = $schema->createTable("violations"); |
370
|
|
|
$violation_table->addColumn |
371
|
|
|
( "id", "integer" |
372
|
|
|
, array("notnull" => true, "unsigned" => true, "autoincrement" => true) |
373
|
|
|
); |
374
|
|
|
$violation_table->addColumn |
375
|
|
|
( "rule_id", "integer" |
376
|
|
|
, array("notnull" => true) |
377
|
|
|
); |
378
|
|
|
$violation_table->addColumn |
379
|
|
|
( "file", "string" |
380
|
|
|
, array("notnull" => true) |
381
|
|
|
); |
382
|
|
|
$violation_table->addColumn |
383
|
|
|
( "line", "string" |
384
|
|
|
, array("notnull" => true) |
385
|
|
|
); |
386
|
|
|
$violation_table->addColumn |
387
|
|
|
( "first_seen", "integer" |
388
|
|
|
, array("notnull" => true) |
389
|
|
|
); |
390
|
|
|
$violation_table->addColumn |
391
|
|
|
( "last_seen", "integer" |
392
|
|
|
, array("notnull" => true) |
393
|
|
|
); |
394
|
|
|
$violation_table->setPrimaryKey(array("id")); |
395
|
|
|
$violation_table->addUniqueIndex(array("rule_id", "file", "line")); |
396
|
|
|
$violation_table->addForeignKeyConstraint |
397
|
|
|
( $rule_table |
398
|
|
|
, array("rule_id") |
399
|
|
|
, array("id") |
400
|
|
|
); |
401
|
|
|
$violation_table->addForeignKeyConstraint |
402
|
|
|
( $run_table |
403
|
|
|
, array("first_seen") |
404
|
|
|
, array("id") |
405
|
|
|
); |
406
|
|
|
$violation_table->addForeignKeyConstraint |
407
|
|
|
( $run_table |
408
|
|
|
, array("last_seen") |
409
|
|
|
, array("id") |
410
|
|
|
); |
411
|
|
|
|
412
|
|
|
return $violation_table; |
413
|
|
|
} |
414
|
|
|
|
415
|
|
|
protected function init_violation_location_table(Schema\Schema $schema, Schema\Table $run_table, Schema\Table $violation_table) { |
416
|
|
|
$violation_location_table = $schema->createTable("violation_locations"); |
417
|
|
|
$violation_location_table->addColumn |
418
|
|
|
( "id", "integer" |
419
|
|
|
, array("notnull" => true, "unsigned" => true, "autoincrement" => true) |
420
|
|
|
); |
421
|
|
|
$violation_location_table->addColumn |
422
|
|
|
( "violation_id", "integer" |
423
|
|
|
, array("notnull" => true) |
424
|
|
|
); |
425
|
|
|
$violation_location_table->addColumn |
426
|
|
|
( "run_id", "integer" |
427
|
|
|
, array("notnull" => true) |
428
|
|
|
); |
429
|
|
|
$violation_location_table->addColumn |
430
|
|
|
( "line_no", "integer" |
431
|
|
|
, array("notnull" => true) |
432
|
|
|
); |
433
|
|
|
$violation_location_table->setPrimaryKey(array("id")); |
434
|
|
|
$violation_location_table->addForeignKeyConstraint |
435
|
|
|
( $violation_table |
436
|
|
|
, array("violation_id") |
437
|
|
|
, array("id") |
438
|
|
|
); |
439
|
|
|
$violation_location_table->addForeignKeyConstraint |
440
|
|
|
( $run_table |
441
|
|
|
, array("run_id") |
442
|
|
|
, array("id") |
443
|
|
|
); |
444
|
|
|
return $violation_location_table; |
445
|
|
|
} |
446
|
|
|
|
447
|
|
|
public function init_database_schema() { |
448
|
|
|
$schema = new Schema\Schema(); |
449
|
|
|
|
450
|
|
|
$run_table = $this->init_run_table($schema); |
451
|
|
|
$rule_table = $this->init_rule_table($schema, $run_table); |
452
|
|
|
$this->init_variable_table($schema); |
453
|
|
|
$violation_table = $this->init_violation_table($schema, $run_table, $rule_table); |
454
|
|
|
$this->init_violation_location_table($schema, $run_table, $violation_table); |
455
|
|
|
|
456
|
|
|
$sync = new SingleDatabaseSynchronizer($this->connection); |
457
|
|
|
$sync->createSchema($schema); |
458
|
|
|
} |
459
|
|
|
} |
460
|
|
|
|
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.