22
22
class SchemaAnalyzer
23
23
{
24
24
private static $ WEIGHT_FK = 1 ;
25
+ private static $ WEIGHT_INHERITANCE_FK = 0.1 ;
25
26
private static $ WEIGHT_JOINTURE_TABLE = 1.5 ;
26
27
27
28
const WEIGHT_IMPORTANT = 0.75 ;
@@ -49,13 +50,15 @@ class SchemaAnalyzer
49
50
private $ cachePrefix ;
50
51
51
52
/**
52
- * Nested arrays containing table => column => cost
53
+ * Nested arrays containing table => column => cost.
54
+ *
53
55
* @var float[][]
54
56
*/
55
57
private $ alteredCosts = [];
56
58
57
59
/**
58
- * Array containing table cost
60
+ * Array containing table cost.
61
+ *
59
62
* @var float[]
60
63
*/
61
64
private $ alteredTableCosts = [];
@@ -124,7 +127,11 @@ private function isJunctionTable(Table $table)
124
127
return false ;
125
128
}
126
129
127
- $ pkColumns = $ table ->getPrimaryKeyColumns ();
130
+ if ($ table ->hasPrimaryKey ()) {
131
+ $ pkColumns = $ table ->getPrimaryKeyColumns ();
132
+ } else {
133
+ $ pkColumns = [];
134
+ }
128
135
129
136
if (count ($ pkColumns ) == 1 && count ($ columns ) == 2 ) {
130
137
return false ;
@@ -170,14 +177,9 @@ private function isJunctionTable(Table $table)
170
177
*/
171
178
public function getShortestPath ($ fromTable , $ toTable )
172
179
{
173
- $ cacheKey = $ this ->cachePrefix .'_shortest_ ' .$ fromTable .'``` ' .$ toTable ;
174
- $ path = $ this ->cache ->fetch ($ cacheKey );
175
- if ($ path === false ) {
176
- $ path = $ this ->getShortestPathWithoutCache ($ fromTable , $ toTable );
177
- $ this ->cache ->save ($ cacheKey , $ path );
178
- }
179
-
180
- return $ path ;
180
+ return $ this ->fromCache ($ this ->cachePrefix .'_shortest_ ' .$ fromTable .'``` ' .$ toTable , function () use ($ fromTable , $ toTable ) {
181
+ return $ this ->getShortestPathWithoutCache ($ fromTable , $ toTable );
182
+ });
181
183
}
182
184
183
185
/**
@@ -255,8 +257,10 @@ private function buildSchemaGraph()
255
257
foreach ($ table ->getForeignKeys () as $ fk ) {
256
258
// Create an undirected edge, with weight = 1
257
259
$ edge = $ graph ->getVertex ($ table ->getName ())->createEdge ($ graph ->getVertex ($ fk ->getForeignTableName ()));
258
- if (isset ($ this ->alteredCosts [$ fk ->getLocalTable ()->getName ()][implode (', ' ,$ fk ->getLocalColumns ())])) {
259
- $ cost = $ this ->alteredCosts [$ fk ->getLocalTable ()->getName ()][implode (', ' ,$ fk ->getLocalColumns ())];
260
+ if (isset ($ this ->alteredCosts [$ fk ->getLocalTable ()->getName ()][implode (', ' , $ fk ->getLocalColumns ())])) {
261
+ $ cost = $ this ->alteredCosts [$ fk ->getLocalTable ()->getName ()][implode (', ' , $ fk ->getLocalColumns ())];
262
+ } elseif ($ this ->isInheritanceRelationship ($ fk )) {
263
+ $ cost = self ::$ WEIGHT_INHERITANCE_FK ;
260
264
} else {
261
265
$ cost = self ::$ WEIGHT_FK ;
262
266
}
@@ -386,21 +390,25 @@ private function getTextualPath(array $path, Vertex $startVertex)
386
390
*
387
391
* @param string $tableName
388
392
* @param string $columnName
389
- * @param float $cost
393
+ * @param float $cost
394
+ *
390
395
* @return $this
391
396
*/
392
- public function setForeignKeyCost ($ tableName , $ columnName , $ cost ) {
397
+ public function setForeignKeyCost ($ tableName , $ columnName , $ cost )
398
+ {
393
399
$ this ->alteredCosts [$ tableName ][$ columnName ] = $ cost ;
394
400
}
395
401
396
402
/**
397
403
* Sets the cost modifier of a table.
398
404
*
399
405
* @param string $tableName
400
- * @param float $cost
406
+ * @param float $cost
407
+ *
401
408
* @return $this
402
409
*/
403
- public function setTableCostModifier ($ tableName , $ cost ) {
410
+ public function setTableCostModifier ($ tableName , $ cost )
411
+ {
404
412
$ this ->alteredTableCosts [$ tableName ] = $ cost ;
405
413
}
406
414
@@ -409,7 +417,8 @@ public function setTableCostModifier($tableName, $cost) {
409
417
*
410
418
* @param array<string, float> $tableCosts The key is the table name, the value is the cost modifier.
411
419
*/
412
- public function setTableCostModifiers (array $ tableCosts ) {
420
+ public function setTableCostModifiers (array $ tableCosts )
421
+ {
413
422
$ this ->alteredTableCosts = $ tableCosts ;
414
423
}
415
424
@@ -418,7 +427,129 @@ public function setTableCostModifiers(array $tableCosts) {
418
427
*
419
428
* @param array<string, array<string, float>> $fkCosts First key is the table name, second key is the column name, the value is the cost.
420
429
*/
421
- public function setForeignKeyCosts (array $ fkCosts ) {
430
+ public function setForeignKeyCosts (array $ fkCosts )
431
+ {
422
432
$ this ->alteredCosts = $ fkCosts ;
423
433
}
434
+
435
+ /**
436
+ * Returns true if this foreign key represents an inheritance relationship,
437
+ * i.e. if this foreign key is based on a primary key.
438
+ *
439
+ * @param ForeignKeyConstraint $fk
440
+ *
441
+ * @return true
442
+ */
443
+ private function isInheritanceRelationship (ForeignKeyConstraint $ fk )
444
+ {
445
+ if (!$ fk ->getLocalTable ()->hasPrimaryKey ()) {
446
+ return false ;
447
+ }
448
+ $ fkColumnNames = $ fk ->getLocalColumns ();
449
+ $ pkColumnNames = $ fk ->getLocalTable ()->getPrimaryKeyColumns ();
450
+
451
+ sort ($ fkColumnNames );
452
+ sort ($ pkColumnNames );
453
+
454
+ return $ fkColumnNames == $ pkColumnNames ;
455
+ }
456
+
457
+ /**
458
+ * If this table is pointing to a parent table (if its primary key is a foreign key pointing on another table),
459
+ * this function will return the pointed table.
460
+ * This function will return null if there is no parent table.
461
+ *
462
+ * @param string $tableName
463
+ *
464
+ * @return string|null
465
+ */
466
+ public function getParentTable ($ tableName )
467
+ {
468
+ return $ this ->fromCache ($ this ->cachePrefix .'_parent_ ' .$ tableName , function () use ($ tableName ) {
469
+ return $ this ->getParentTableWithoutCache ($ tableName );
470
+ });
471
+ }
472
+
473
+ /**
474
+ * If this table is pointing to a parent table (if its primary key is a foreign key pointing on another table),
475
+ * this function will return the pointed table.
476
+ * This function will return null if there is no parent table.
477
+ *
478
+ * @param string $tableName
479
+ *
480
+ * @return string|null
481
+ */
482
+ private function getParentTableWithoutCache ($ tableName )
483
+ {
484
+ $ table = $ this ->getSchema ()->getTable ($ tableName );
485
+ foreach ($ table ->getForeignKeys () as $ fk ) {
486
+ if ($ this ->isInheritanceRelationship ($ fk )) {
487
+ return $ fk ->getForeignTableName ();
488
+ }
489
+ }
490
+
491
+ return ;
492
+ }
493
+
494
+ /**
495
+ * If this table is pointed by children tables (if other child tables have a primary key that is also a
496
+ * foreign key to this table), this function will return the list of child tables.
497
+ * This function will return an empty array if there are no children tables.
498
+ *
499
+ * @param string $tableName
500
+ *
501
+ * @return string[]
502
+ */
503
+ public function getChildrenTables ($ tableName )
504
+ {
505
+ return $ this ->fromCache ($ this ->cachePrefix .'_children_ ' .$ tableName , function () use ($ tableName ) {
506
+ return $ this ->getChildrenTablesWithoutCache ($ tableName );
507
+ });
508
+ }
509
+
510
+ /**
511
+ * If this table is pointed by children tables (if other child tables have a primary key that is also a
512
+ * foreign key to this table), this function will return the list of child tables.
513
+ * This function will return an empty array if there are no children tables.
514
+ *
515
+ * @param string $tableName
516
+ *
517
+ * @return string[]
518
+ */
519
+ private function getChildrenTablesWithoutCache ($ tableName )
520
+ {
521
+ $ schema = $ this ->getSchema ();
522
+ $ children = [];
523
+ foreach ($ schema ->getTables () as $ table ) {
524
+ if ($ table ->getName () === $ tableName ) {
525
+ continue ;
526
+ }
527
+ foreach ($ table ->getForeignKeys () as $ fk ) {
528
+ if ($ fk ->getForeignTableName () === $ tableName && $ this ->isInheritanceRelationship ($ fk )) {
529
+ $ children [] = $ fk ->getLocalTableName ();
530
+ }
531
+ }
532
+ }
533
+
534
+ return $ children ;
535
+ }
536
+
537
+ /**
538
+ * Returns an item from cache or computes it using $closure and puts it in cache.
539
+ *
540
+ * @param string $key
541
+ * @param callable $closure
542
+ *
543
+ * @return mixed
544
+ */
545
+ private function fromCache ($ key , callable $ closure )
546
+ {
547
+ $ item = $ this ->cache ->fetch ($ key );
548
+ if ($ item === false ) {
549
+ $ item = $ closure ();
550
+ $ this ->cache ->save ($ key , $ item );
551
+ }
552
+
553
+ return $ item ;
554
+ }
424
555
}
0 commit comments