-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #25 from telkins/develop
Raise minimum support to Laravel 10 and PHP8.1
- Loading branch information
Showing
15 changed files
with
432 additions
and
362 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Telkins\Dag\Actions; | ||
|
||
use Illuminate\Support\Collection; | ||
use Telkins\Dag\Concerns\EdgeData; | ||
use Telkins\Dag\Concerns\UsesDagConfig; | ||
use Telkins\Dag\Exceptions\CircularReferenceException; | ||
use Telkins\Dag\Exceptions\TooManyHopsException; | ||
use Telkins\Dag\Models\DagEdge; | ||
|
||
class AddDagEdge | ||
{ | ||
use UsesDagConfig; | ||
|
||
public function __construct( | ||
private CreateCompleteEdge $createCompleteEdge, | ||
) {} | ||
|
||
/** | ||
* @throws CircularReferenceException | ||
* @throws TooManyHopsException | ||
*/ | ||
public function __invoke(EdgeData $edgeData, int $maxHops): ?Collection | ||
{ | ||
if ($this->edgeExists($edgeData)) { | ||
return null; | ||
} | ||
|
||
$this->guardAgainstCircularRelation($edgeData); | ||
|
||
return tap( | ||
$this->getNewlyInsertedEdges(($this->createCompleteEdge)($edgeData)), | ||
fn ($newEdges) => $this->guardAgainstExceedingMaximumHops($newEdges, $maxHops), | ||
); | ||
} | ||
|
||
protected function edgeExists(EdgeData $edgeData): bool | ||
{ | ||
$edgeClass = $this->dagEdgeModel(); | ||
|
||
return $edgeClass::where([ | ||
['start_vertex', $edgeData->startVertex], | ||
['end_vertex', $edgeData->endVertex], | ||
['hops', 0], | ||
['source', $edgeData->source], | ||
])->count() > 0; | ||
} | ||
|
||
/** | ||
* @throws CircularReferenceException | ||
*/ | ||
protected function guardAgainstCircularRelation(EdgeData $edgeData): void | ||
{ | ||
if ($edgeData->startVertex === $edgeData->endVertex) { | ||
throw new CircularReferenceException(); | ||
} | ||
|
||
$edgeClass = $this->dagEdgeModel(); | ||
|
||
if ($edgeClass::where([ | ||
['start_vertex', $edgeData->endVertex], | ||
['end_vertex', $edgeData->startVertex], | ||
['source', $edgeData->source], | ||
])->count() > 0) { | ||
throw new CircularReferenceException(); | ||
} | ||
} | ||
|
||
protected function getNewlyInsertedEdges(DagEdge $edge): Collection | ||
{ | ||
$edgeClass = $this->dagEdgeModel(); | ||
|
||
return $edgeClass::where('direct_edge_id', $edge->id) | ||
->orderBy('hops') | ||
->get(); | ||
} | ||
|
||
/** | ||
* @throws TooManyHopsException | ||
*/ | ||
protected function guardAgainstExceedingMaximumHops(Collection $newEdges, int $maxHops): void | ||
{ | ||
if ($newEdges->isNotEmpty() && ($newEdges->last()->hops > $maxHops)) { | ||
throw TooManyHopsException::make($maxHops); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Telkins\Dag\Actions; | ||
|
||
use Telkins\Dag\Concerns\EdgeData; | ||
use Telkins\Dag\Concerns\UsesDagConfig; | ||
use Telkins\Dag\Models\DagEdge; | ||
|
||
class CreateCompleteEdge | ||
{ | ||
use UsesDagConfig; | ||
|
||
public function __construct( | ||
private CreateDirectEdge $createDirectEdge, | ||
private CreateImpliedEdges $createImpliedEdges, | ||
) {} | ||
|
||
public function __invoke(EdgeData $edgeData): DagEdge | ||
{ | ||
return tap( | ||
($this->createDirectEdge)($edgeData), | ||
fn ($edge) => ($this->createImpliedEdges)($edge, $edgeData), | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Telkins\Dag\Actions; | ||
|
||
use Telkins\Dag\Concerns\EdgeData; | ||
use Telkins\Dag\Concerns\UsesDagConfig; | ||
use Telkins\Dag\Models\DagEdge; | ||
|
||
class CreateDirectEdge | ||
{ | ||
use UsesDagConfig; | ||
|
||
public function __invoke(EdgeData $edgeData): DagEdge | ||
{ | ||
return tap( | ||
$this->createEdge($edgeData), | ||
fn ($edge) => $this->updateNewEdge($edge), | ||
); | ||
} | ||
|
||
private function createEdge(EdgeData $edgeData): DagEdge | ||
{ | ||
$edgeClass = $this->dagEdgeModel(); | ||
|
||
return $edgeClass::create([ | ||
'start_vertex' => $edgeData->startVertex, | ||
'end_vertex' => $edgeData->endVertex, | ||
'hops' => 0, | ||
'source' => $edgeData->source, | ||
]); | ||
} | ||
|
||
private function updateNewEdge(DagEdge $edge): void | ||
{ | ||
$edge->update([ | ||
'entry_edge_id' => $edge->id, | ||
'exit_edge_id' => $edge->id, | ||
'direct_edge_id' => $edge->id, | ||
]); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Telkins\Dag\Actions; | ||
|
||
use Illuminate\Database\Query\Builder; | ||
use Illuminate\Support\Facades\DB; | ||
use Telkins\Dag\Concerns\EdgeData; | ||
use Telkins\Dag\Concerns\UsesDagConfig; | ||
use Telkins\Dag\Models\DagEdge; | ||
|
||
class CreateImpliedEdges | ||
{ | ||
use UsesDagConfig; | ||
|
||
public function __invoke(DagEdge $edge, EdgeData $edgeData): void | ||
{ | ||
$this->createAsIncomingEdgesToB($edge, $edgeData); | ||
|
||
$this->createAToBsOutgoingEdges($edge, $edgeData); | ||
|
||
$this->createAsIncomingEdgesToEndVertexOfBsOutgoingEdges($edge, $edgeData); | ||
} | ||
|
||
protected function createAsIncomingEdgesToB(DagEdge $edge, EdgeData $edgeData): void | ||
{ | ||
$select = DB::connection($edgeData->connection)->table($edgeData->tableName) | ||
->select([ | ||
'id as entry_edge_id', | ||
DB::connection($edgeData->connection)->raw("{$edge->id} as direct_edge_id"), | ||
DB::connection($edgeData->connection)->raw("{$edge->id} as exit_edge_id"), | ||
'start_vertex', | ||
DB::connection($edgeData->connection)->raw("{$edgeData->endVertex} as end_vertex"), | ||
DB::connection($edgeData->connection)->raw('(hops + 1) as hops'), | ||
DB::connection($edgeData->connection)->raw("'{$edgeData->source}' as source"), | ||
])->where([ | ||
['end_vertex', $edgeData->startVertex], | ||
['source', $edgeData->source], | ||
]); | ||
|
||
$this->executeInsert($select, $edgeData); | ||
} | ||
|
||
protected function createAToBsOutgoingEdges(DagEdge $edge, EdgeData $edgeData): void | ||
{ | ||
$select = DB::connection($edgeData->connection)->table($edgeData->tableName) | ||
->select([ | ||
DB::connection($edgeData->connection)->raw("{$edge->id} as entry_edge_id"), | ||
DB::connection($edgeData->connection)->raw("{$edge->id} as direct_edge_id"), | ||
'id as exit_edge_id', | ||
DB::connection($edgeData->connection)->raw("{$edgeData->startVertex} as start_vertex"), | ||
'end_vertex', | ||
DB::connection($edgeData->connection)->raw('(hops + 1) as hops'), | ||
DB::connection($edgeData->connection)->raw("'{$edgeData->source}' as source"), | ||
])->where([ | ||
['start_vertex', $edgeData->endVertex], | ||
['source', $edgeData->source], | ||
]); | ||
|
||
$this->executeInsert($select, $edgeData); | ||
} | ||
|
||
protected function createAsIncomingEdgesToEndVertexOfBsOutgoingEdges(DagEdge $edge, EdgeData $edgeData): void | ||
{ | ||
$select = DB::connection($edgeData->connection)->table($edgeData->tableName.' as a') | ||
->select([ | ||
DB::connection($edgeData->connection)->raw('a.id as entry_edge_id'), | ||
DB::connection($edgeData->connection)->raw("{$edge->id} as direct_edge_id"), | ||
'b.id as exit_edge_id', | ||
'a.start_vertex', | ||
'b.end_vertex', | ||
DB::connection($edgeData->connection)->raw('(a.hops + b.hops + 2) as hops'), | ||
DB::connection($edgeData->connection)->raw("'{$edgeData->source}' as source"), | ||
])->crossJoin($edgeData->tableName.' as b') | ||
->where([ | ||
['a.end_vertex', $edgeData->startVertex], | ||
['b.start_vertex', $edgeData->endVertex], | ||
['a.source', $edgeData->source], | ||
['b.source', $edgeData->source], | ||
]); | ||
|
||
$this->executeInsert($select, $edgeData); | ||
} | ||
|
||
protected function executeInsert(Builder $select, EdgeData $edgeData): void | ||
{ | ||
$bindings = $select->getBindings(); | ||
|
||
$insertQuery = 'INSERT into `'.$edgeData->tableName.'` ( | ||
`entry_edge_id`, | ||
`direct_edge_id`, | ||
`exit_edge_id`, | ||
`start_vertex`, | ||
`end_vertex`, | ||
`hops`, | ||
`source`) ' | ||
. $select->toSql(); | ||
|
||
DB::connection($edgeData->connection)->insert($insertQuery, $bindings); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Telkins\Dag\Actions; | ||
|
||
use Illuminate\Support\Collection; | ||
use Illuminate\Support\Facades\DB; | ||
use Telkins\Dag\Concerns\UsesDagConfig; | ||
use Telkins\Dag\Models\DagEdge; | ||
|
||
/** | ||
* Find and return all edge table entry IDs for a given edge. | ||
*/ | ||
class GetAllEdgeEntryIds | ||
{ | ||
use UsesDagConfig; | ||
|
||
public function __invoke(DagEdge $edge, string $tableName, ?string $connection): Collection | ||
{ | ||
/** | ||
* First, collect the "rows that were originally inserted...for this | ||
* direct edge". | ||
*/ | ||
$originalIds = DB::connection($connection)->table($tableName) | ||
->select('id') | ||
->where('direct_edge_id', $edge->id) | ||
->get() | ||
->pluck('id'); | ||
|
||
/** | ||
* Next, "scan and find all dependent rows that were inserted | ||
* afterwards". | ||
*/ | ||
do { | ||
$dependentIds = DB::connection($connection)->table($tableName) | ||
->select('id') | ||
->where('hops', '>', 0) | ||
->whereNotIn('id', $originalIds) | ||
->where(function ($query) use ($originalIds) { | ||
$query->whereIn('entry_edge_id', $originalIds) | ||
->orWhereIn('exit_edge_id', $originalIds); | ||
}) | ||
->get() | ||
->pluck('id'); | ||
|
||
$originalIds = $originalIds->merge($dependentIds); | ||
} while ($dependentIds->count()); | ||
|
||
return $originalIds; | ||
} | ||
} |
Oops, something went wrong.