2023-10-02 23:10:49 +03:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
namespace Lucent\Query;
|
|
|
|
|
|
|
|
|
|
use Illuminate\Database\Query\Builder;
|
|
|
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
|
use Lucent\Edge\Edge;
|
|
|
|
|
use Lucent\Primitive\Collection;
|
2023-10-15 23:40:34 +03:00
|
|
|
use Lucent\Query\DatabaseGraph\DatabaseGraph;
|
2023-10-13 21:06:23 +03:00
|
|
|
use Lucent\Query\Filter\AndFilter;
|
|
|
|
|
use Lucent\Query\Filter\OrFilter;
|
2023-10-02 23:10:49 +03:00
|
|
|
use Lucent\Record\InputFormatter;
|
|
|
|
|
use Lucent\Record\QueryRecord;
|
|
|
|
|
use Lucent\Record\Record;
|
|
|
|
|
|
|
|
|
|
final class Query
|
|
|
|
|
{
|
|
|
|
|
|
2023-10-13 21:06:23 +03:00
|
|
|
/**
|
|
|
|
|
* @var array<AndFilter> $filters
|
|
|
|
|
*/
|
|
|
|
|
public array $filters;
|
2023-10-02 23:10:49 +03:00
|
|
|
public QueryOptions $options;
|
|
|
|
|
|
|
|
|
|
public function __construct(
|
2023-10-13 21:06:23 +03:00
|
|
|
public readonly FilterParser $filterParser,
|
2023-10-02 23:10:49 +03:00
|
|
|
public readonly InputFormatter $inputFormatter,
|
2023-10-15 23:40:34 +03:00
|
|
|
public readonly DatabaseGraph $databaseGraph,
|
2023-10-02 23:10:49 +03:00
|
|
|
)
|
|
|
|
|
{
|
|
|
|
|
$this->options = new QueryOptions();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function filter(array $filterArguments): Query
|
|
|
|
|
{
|
2023-10-13 21:06:23 +03:00
|
|
|
$this->filters[] = new AndFilter($filterArguments);
|
|
|
|
|
return $this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function orFilter(array $filterArguments): Query
|
|
|
|
|
{
|
|
|
|
|
$this->filters[] = new OrFilter($filterArguments);
|
2023-10-02 23:10:49 +03:00
|
|
|
return $this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public function run(): Graph
|
|
|
|
|
{
|
|
|
|
|
$resultsRecords = $this->mainQuery();
|
|
|
|
|
|
|
|
|
|
$ids = array_map(function ($rec) {
|
|
|
|
|
return $rec->id;
|
|
|
|
|
}, $resultsRecords);
|
|
|
|
|
|
|
|
|
|
$resultChildrenEdgesTargetIds = [];
|
|
|
|
|
$resultChildrenEdges = [];
|
|
|
|
|
if ($this->options->childrenDepth > 0 && !empty($ids)) {
|
|
|
|
|
$resultChildrenEdges = $this->getChildren($ids);
|
|
|
|
|
$resultChildrenEdgesTargetIds = array_map(fn($e) => $e->target, $resultChildrenEdges);
|
|
|
|
|
}
|
|
|
|
|
$resultParentSourceTargetIds = [];
|
|
|
|
|
$resultParentEdges = [];
|
|
|
|
|
if ($this->options->parentsDepth > 0 && !empty($ids)) {
|
|
|
|
|
$resultParentEdges = $this->getParents($ids);
|
|
|
|
|
$resultParentSourceTargetIds = array_map(fn($e) => $e->source, $resultParentEdges);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$edgesIds = collect($resultParentSourceTargetIds)->merge($resultChildrenEdgesTargetIds)->unique()->values()->toArray();
|
|
|
|
|
$edgeRecords = [];
|
|
|
|
|
if (!empty($edgesIds)) {
|
|
|
|
|
$edgeRecords = DB::table('records')
|
|
|
|
|
->whereIn("id", $edgesIds)
|
2023-10-04 13:32:30 +03:00
|
|
|
->whereIn("status", $this->options->status)
|
2023-10-02 23:10:49 +03:00
|
|
|
->get()->toArray();
|
|
|
|
|
}
|
|
|
|
|
$resultsRecordsUnique = collect(array_merge($resultsRecords, $edgeRecords))->unique("id")->values()->toArray();
|
2023-10-13 21:06:23 +03:00
|
|
|
// $resultEdges = collect(array_merge($resultChildrenEdges, $resultParentEdges))
|
|
|
|
|
// ->unique(fn($edge) => $edge->source . $edge->target . $edge->field)
|
|
|
|
|
// ->toArray();
|
2023-10-02 23:10:49 +03:00
|
|
|
|
2023-12-12 14:08:39 +02:00
|
|
|
$formattedRecords = $this->formatRecords($resultsRecordsUnique, $resultChildrenEdges, $resultParentEdges);
|
2023-10-13 21:06:23 +03:00
|
|
|
$this->reset();
|
2023-12-12 14:08:39 +02:00
|
|
|
return $formattedRecords;
|
2023-10-02 23:10:49 +03:00
|
|
|
|
2023-10-13 21:06:23 +03:00
|
|
|
}
|
2023-10-02 23:10:49 +03:00
|
|
|
|
2023-10-13 21:06:23 +03:00
|
|
|
private function reset()
|
|
|
|
|
{
|
|
|
|
|
$this->options = new QueryOptions();
|
|
|
|
|
$this->filters = [];
|
2023-10-02 23:10:49 +03:00
|
|
|
}
|
|
|
|
|
|
2023-10-13 21:06:23 +03:00
|
|
|
private function formatRecords(array $records, array $edges, array $parentEdges): Graph
|
2023-10-02 23:10:49 +03:00
|
|
|
{
|
|
|
|
|
$queryRecords = collect($records)->map(function ($recordData) {
|
|
|
|
|
|
|
|
|
|
$record = Record::fromDB($recordData);
|
2023-10-04 13:32:30 +03:00
|
|
|
$record->data = $this->inputFormatter->fill($record->schema, $record->data);
|
2023-10-02 23:10:49 +03:00
|
|
|
$queryRecord = QueryRecord::fromRecord($record);
|
|
|
|
|
$queryRecord->isRoot = data_get($recordData, "isRoot") === true;
|
|
|
|
|
return $queryRecord;
|
|
|
|
|
})->toArray();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
$queryEdges = collect($edges)->map(function ($edgeData) {
|
|
|
|
|
|
|
|
|
|
return Edge::fromArray((array)$edgeData);
|
|
|
|
|
|
2023-12-12 14:08:39 +02:00
|
|
|
})->sortBy(['depth', 'asc'], ['rank', 'desc'])->values()->toArray();
|
2023-10-02 23:10:49 +03:00
|
|
|
|
2023-10-13 21:06:23 +03:00
|
|
|
$queryParentEdges = collect($parentEdges)->map(function ($edgeData) {
|
|
|
|
|
|
|
|
|
|
return Edge::fromArray((array)$edgeData);
|
|
|
|
|
|
2023-12-12 14:08:39 +02:00
|
|
|
})->sortBy(['depth', 'asc'], ['rank', 'desc'])->values()->toArray();
|
|
|
|
|
|
2023-10-13 21:06:23 +03:00
|
|
|
|
2023-10-02 23:10:49 +03:00
|
|
|
|
|
|
|
|
return new Graph(
|
|
|
|
|
new Collection($queryRecords),
|
|
|
|
|
new Collection($queryEdges),
|
2023-10-13 21:06:23 +03:00
|
|
|
new Collection($queryParentEdges),
|
2023-12-12 14:08:39 +02:00
|
|
|
$this->options,
|
2023-10-02 23:10:49 +03:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public function tree(): Collection
|
|
|
|
|
{
|
|
|
|
|
return $this->run()->tree();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private function parseFilters(Builder $query): Builder
|
|
|
|
|
{
|
2023-10-13 21:06:23 +03:00
|
|
|
foreach ($this->filters as $filter) {
|
|
|
|
|
$query = $this->filterParser->parse($query, $filter);
|
2023-10-02 23:10:49 +03:00
|
|
|
}
|
2023-10-04 13:32:30 +03:00
|
|
|
$query->whereIn("status", $this->options->status);
|
2023-10-02 23:10:49 +03:00
|
|
|
return $query;
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-13 21:06:23 +03:00
|
|
|
|
2023-10-02 23:10:49 +03:00
|
|
|
private function mainQuery(): array
|
|
|
|
|
{
|
|
|
|
|
$query = DB::table("records");
|
|
|
|
|
$query = $this->parseFilters($query);
|
2023-10-13 21:06:23 +03:00
|
|
|
if ($this->options->limit > 0) {
|
2023-10-08 02:12:02 +03:00
|
|
|
$query->limit($this->options->limit);
|
|
|
|
|
$query->offset($this->options->skip);
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-02 23:10:49 +03:00
|
|
|
$query = $this->orderByQuery($query);
|
2023-10-23 19:43:59 +03:00
|
|
|
|
2023-10-02 23:10:49 +03:00
|
|
|
return $query->get()->map(function ($r) {
|
|
|
|
|
$r->isRoot = true;
|
|
|
|
|
return $r;
|
|
|
|
|
})->toArray();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private
|
|
|
|
|
function getChildren(array $ids): array
|
|
|
|
|
{
|
2023-10-15 23:40:34 +03:00
|
|
|
return $this->databaseGraph->getChildren($ids, $this->options);
|
2023-10-02 23:10:49 +03:00
|
|
|
}
|
|
|
|
|
|
2023-10-13 21:06:23 +03:00
|
|
|
private function getParents(array $ids): array
|
2023-10-02 23:10:49 +03:00
|
|
|
{
|
2023-10-15 23:40:34 +03:00
|
|
|
return $this->databaseGraph->getParents($ids, $this->options);
|
2023-10-02 23:10:49 +03:00
|
|
|
}
|
|
|
|
|
|
2023-10-04 13:32:30 +03:00
|
|
|
|
2023-10-02 23:10:49 +03:00
|
|
|
public
|
|
|
|
|
function runWithCount(): Graph
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
$query = DB::table("records");
|
|
|
|
|
$query = $this->parseFilters($query);
|
|
|
|
|
$graph = $this->run();
|
|
|
|
|
$graph->total = $query->count();
|
|
|
|
|
return $graph;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public
|
|
|
|
|
function limit(int $limit): Query
|
|
|
|
|
{
|
|
|
|
|
$this->options->limit = $limit;
|
|
|
|
|
return $this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public
|
|
|
|
|
function skip(int $skip): Query
|
|
|
|
|
{
|
|
|
|
|
$this->options->skip = $skip;
|
|
|
|
|
return $this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function childrenDepth(int $depth): Query
|
|
|
|
|
{
|
|
|
|
|
$this->options->childrenDepth = $depth;
|
|
|
|
|
return $this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function childrenLimit(int $limit): Query
|
|
|
|
|
{
|
|
|
|
|
$this->options->childrenLimit = $limit;
|
|
|
|
|
return $this;
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-13 21:06:23 +03:00
|
|
|
public function childrenFields(array $fields): Query
|
|
|
|
|
{
|
|
|
|
|
$this->options->childrenFields = $fields;
|
|
|
|
|
return $this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function parentFields(array $fields): Query
|
|
|
|
|
{
|
|
|
|
|
$this->options->parentFields = $fields;
|
|
|
|
|
return $this;
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-02 23:10:49 +03:00
|
|
|
public function parentsDepth(int $depth): Query
|
|
|
|
|
{
|
|
|
|
|
$this->options->parentsDepth = $depth;
|
|
|
|
|
return $this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function parentsLimit(int $limit): Query
|
|
|
|
|
{
|
|
|
|
|
$this->options->parentsLimit = $limit;
|
|
|
|
|
return $this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public function sort(string $sort): Query
|
|
|
|
|
{
|
|
|
|
|
$this->options->sort[] = $sort;
|
|
|
|
|
return $this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function status(array $status): Query
|
|
|
|
|
{
|
|
|
|
|
$this->options->status = $status;
|
|
|
|
|
return $this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public
|
|
|
|
|
function orderByQuery(Builder $query): Builder
|
|
|
|
|
{
|
|
|
|
|
foreach ($this->options->sort as $item) {
|
|
|
|
|
$field = str_replace(".", "->", ltrim($item, '-'));
|
|
|
|
|
$dir = str_starts_with($item, '-') ? "desc" : "asc";
|
|
|
|
|
if ($field) {
|
|
|
|
|
$query->orderBy($field, $dir);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return $query;
|
|
|
|
|
}
|
|
|
|
|
}
|