init
This commit is contained in:
@@ -0,0 +1,292 @@
|
||||
<?php
|
||||
|
||||
namespace Lucent\Query;
|
||||
|
||||
use Illuminate\Database\Query\Builder;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Lucent\Channel\ChannelService;
|
||||
use Lucent\Edge\Edge;
|
||||
use Lucent\Primitive\Collection;
|
||||
use Lucent\Record\InputFormatter;
|
||||
use Lucent\Record\QueryRecord;
|
||||
use Lucent\Record\Record;
|
||||
|
||||
final class Query
|
||||
{
|
||||
|
||||
public Filter $filter;
|
||||
public QueryOptions $options;
|
||||
|
||||
public function __construct(
|
||||
public readonly ChannelService $channelService,
|
||||
public readonly InputFormatter $inputFormatter,
|
||||
)
|
||||
{
|
||||
$this->options = new QueryOptions();
|
||||
}
|
||||
|
||||
public function filter(array $filterArguments): Query
|
||||
{
|
||||
$this->filter = new Filter($filterArguments);
|
||||
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)
|
||||
->whereIn("_sys->status", $this->options->status)
|
||||
->get()->toArray();
|
||||
}
|
||||
$resultsRecordsUnique = collect(array_merge($resultsRecords, $edgeRecords))->unique("id")->values()->toArray();
|
||||
$resultEdges = collect(array_merge($resultChildrenEdges, $resultParentEdges))
|
||||
->unique(fn($edge) => $edge->source . $edge->target . $edge->field)
|
||||
->toArray();
|
||||
|
||||
|
||||
return $this->formatRecords($resultsRecordsUnique, $resultEdges);
|
||||
|
||||
}
|
||||
|
||||
private function formatRecords(array $records, array $edges): Graph
|
||||
{
|
||||
$queryRecords = collect($records)->map(function ($recordData) {
|
||||
|
||||
$record = Record::fromDB($recordData);
|
||||
$record->data = $this->inputFormatter->fill($record->_sys->schema, $record->data);
|
||||
$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);
|
||||
|
||||
})->sortBy("rank")->values()->toArray();
|
||||
|
||||
|
||||
return new Graph(
|
||||
new Collection($queryRecords),
|
||||
new Collection($queryEdges),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
public function tree(): Collection
|
||||
{
|
||||
return $this->run()->tree();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @throws SubqueryNoResultException
|
||||
*/
|
||||
private function parseFilters(Builder $query): Builder
|
||||
{
|
||||
$filters = $this->filter->run(new Query($this->channelService, $this->inputFormatter));
|
||||
$ignoredFilters = [];
|
||||
foreach ($filters as $filter) {
|
||||
if (in_array($filter["field"], $ignoredFilters)) {
|
||||
continue;
|
||||
} else if ($filter["operator"] == "in") {
|
||||
$query->whereIn($filter["field"], $filter["value"]);
|
||||
} else if ($filter["operator"] == "nin") {
|
||||
$query->whereNotIn($filter["field"], $filter["value"]);
|
||||
} elseif ($filter["operator"] == "eqobject") {
|
||||
|
||||
$object = $filter["value"];
|
||||
// unset related filters used here
|
||||
$addToIgnored = collect($filters)
|
||||
->filter(fn($f) => str_starts_with($f["field"], $object))
|
||||
->values()
|
||||
->map(fn($f) => $f["field"])
|
||||
->toArray();
|
||||
|
||||
$ignoredFilters = array_merge($ignoredFilters, $addToIgnored);
|
||||
|
||||
$objectFilters = collect($filters)
|
||||
->filter(fn($f) => str_starts_with($f["field"], $object))
|
||||
->values()
|
||||
->reduce(function ($c, $f) use ($object) {
|
||||
$field = str_replace($object . "->", "", $f["field"]);
|
||||
$c[$field] = $f["value"];
|
||||
return $c;
|
||||
});
|
||||
|
||||
// target result
|
||||
// filter[data.previousNames_object]=previousNames&filter[previousNames.name_eq]=alpha&filter[previousNames.id_eqnum]=24
|
||||
// $query->whereJsonContains("data->previousNames", [["name" => "alpha", "id" => 24]]);
|
||||
// $query->whereJsonContains($filter["field"], [$objectFilters]);
|
||||
} else {
|
||||
|
||||
$query->where($filter["field"], $filter["operator"], $filter["value"]);
|
||||
}
|
||||
}
|
||||
|
||||
$query->whereIn("_sys->status", $this->options->status);
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SubqueryNoResultException
|
||||
*/
|
||||
private function mainQuery(): array
|
||||
{
|
||||
$query = DB::table("records");
|
||||
$query = $this->parseFilters($query);
|
||||
$query->limit($this->options->limit);
|
||||
$query->offset($this->options->skip);
|
||||
$query = $this->orderByQuery($query);
|
||||
|
||||
return $query->get()->map(function ($r) {
|
||||
$r->isRoot = true;
|
||||
return $r;
|
||||
})->toArray();
|
||||
}
|
||||
|
||||
private
|
||||
function getChildren(array $ids): array
|
||||
{
|
||||
$subquery = DB::table('edges AS g')
|
||||
->select(DB::raw('g.source,g.target,g.rank,"g"."sourceSchema","g"."targetSchema",g.field, 0 as depth '))
|
||||
->whereIn('source', $ids)
|
||||
->limit($this->options->childrenLimit)
|
||||
->unionAll(
|
||||
DB::table(DB::raw("edges AS g, search_graph AS sg "))
|
||||
->selectRaw('g.source,g.target,g.rank,"g"."sourceSchema","g"."targetSchema",g.field,sg.depth + 1 as depth')
|
||||
->whereRaw("g.source = sg.target")
|
||||
->where("sg.depth", "<=", $this->options->childrenDepth)
|
||||
->orderBy("rank")
|
||||
);
|
||||
|
||||
return DB::table('search_graph')
|
||||
// ->select(DB::raw("*, 1 as depth "))
|
||||
->withRecursiveExpression('search_graph', $subquery)
|
||||
->get()->toArray();
|
||||
}
|
||||
|
||||
private
|
||||
function getParents(array $ids): array
|
||||
{
|
||||
$subquery = DB::table('edges AS g')
|
||||
->select(DB::raw('g.source,g.target,g.rank,"g"."sourceSchema","g"."targetSchema",g.field, 0 as depth '))
|
||||
->limit($this->options->parentsLimit)
|
||||
->whereIn('g.target', $ids)
|
||||
->unionAll(
|
||||
DB::table(DB::raw("edges AS g, search_graph AS sg "))
|
||||
->selectRaw('g.source,g.target,g.rank,"g"."sourceSchema","g"."targetSchema",g.field,sg.depth + 1 as depth')
|
||||
->whereRaw("g.target = sg.source")
|
||||
->where("sg.depth", "<=", $this->options->parentsDepth)
|
||||
->orderBy("rank")
|
||||
);
|
||||
|
||||
return DB::table('search_graph')
|
||||
// ->select(DB::raw('sg.source,sg.target,sg.rank,sg."sourceSchema",sg."targetSchema",sg.field,sg.depth'))
|
||||
->withRecursiveExpression('search_graph', $subquery)
|
||||
->get()->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SubqueryNoResultException
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user