query and graph

This commit is contained in:
2024-03-23 21:12:07 +02:00
parent b8efa5f586
commit c649077e37
33 changed files with 4335 additions and 908 deletions
+2 -1
View File
@@ -19,7 +19,8 @@
"symfony/yaml": "^7.0"
},
"require-dev": {
"phpstan/phpstan": "^1.8"
"phpstan/phpstan": "^1.8",
"laravel/framework": "^10.10"
},
"autoload": {
"psr-4": {
Generated
+3819 -473
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -18,7 +18,7 @@ return new class extends Migration {
$table->string('status');
$table->jsonb('data');
$table->jsonb('_sys');
$table->jsonb('_file');
$table->jsonb('_file')->nullable();
$table->index(['schema', 'status']);
});
+17
View File
@@ -5,6 +5,7 @@ namespace Lucent\Edge;
use Lucent\LucentException;
use Lucent\Validator\Validator as LucentValidator;
use PhpOption\Option;
use stdClass;
final class Edge
{
@@ -63,4 +64,20 @@ final class Edge
depth: data_get($data, 'depth', 0),
);
}
public static function fromDB(stdClass $data): Edge
{
return new Edge(
source: data_get($data, 'source'),
target: data_get($data, 'target'),
sourceSchema: data_get($data, 'sourceSchema'),
targetSchema: data_get($data, 'targetSchema'),
field: data_get($data, 'field'),
data: Option::fromValue(data_get($data,"data")),
rank: data_get($data, 'rank'),
depth: data_get($data, 'depth', 0),
);
}
}
+12 -2
View File
@@ -5,10 +5,11 @@ namespace Lucent\File;
use Illuminate\Http\UploadedFile;
use Lucent\Channel\ChannelService;
use Lucent\LucentException;
use Lucent\Record\File;
use Lucent\Record\FileInfo;
use Lucent\Record\QueryRecord;
use Lucent\Schema\Schema\Schema;
use Lucent\Schema\Schema\Type;
use Lucent\Support\Result\Result;
class FileService
{
@@ -24,6 +25,15 @@ class FileService
return $this->channelService->channel->url. "/storage/".$file->_file->path;
}
/**
* @param Schema $schema
* @param string $uploadFromUrl
* @return Result<FileInfo|string>
*/
public function createFromUrl(Schema $schema, string $uploadFromUrl): Result{
}
/**
* @throws LucentException
*/
@@ -48,7 +58,7 @@ class FileService
}
return new FileUploadResult(
recordFile: File::fromArray($file), duplicateId: "", isDuplicate: false
recordFile: FileInfo::fromArray($file), duplicateId: "", isDuplicate: false
);
}
+4 -4
View File
@@ -2,15 +2,15 @@
namespace Lucent\File;
use Lucent\Record\File;
use Lucent\Record\FileInfo;
class FileUploadResult
{
public function __construct(
public ?File $recordFile,
public string $duplicateId,
public bool $isDuplicate,
public ?FileInfo $recordFile,
public string $duplicateId,
public bool $isDuplicate,
)
{
+1 -1
View File
@@ -10,7 +10,7 @@ use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Intervention\Image\ImageManagerStatic;
use Lucent\LucentException;
use Lucent\Record\File as RecordFile;
use Lucent\Record\FileInfo as RecordFile;
use Lucent\Schema\Schema\Schema;
use Spatie\ImageOptimizer\OptimizerChainFactory;
+10
View File
@@ -0,0 +1,10 @@
<?php namespace Lucent\Http;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Routing\Controller as BaseController;
class Controller extends BaseController
{
use AuthorizesRequests, ValidatesRequests;
}
+1 -1
View File
@@ -2,7 +2,7 @@
namespace Lucent\Http\Controller;
use App\Http\Controllers\Controller;
use Lucent\Http\Controller;
use Illuminate\Http\Request;
use Lucent\Account\AuthService;
use Lucent\Svelte\Svelte;
+22 -22
View File
@@ -2,7 +2,7 @@
namespace Lucent\Http\Controller\Api;
use App\Http\Controllers\Controller;
use Lucent\Http\Controller;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Lucent\Edge\EdgeService;
@@ -12,26 +12,26 @@ use function Lucent\Response\ok;
class EdgeController extends Controller
{
public function create(Request $request): Response
{
try {
$edge = EdgeService::create(
source: $request->input("source"),
target: $request->input("target"),
sourceSchema: $request->input("sourceSchema"),
targetSchema: $request->input("targetSchema"),
field: $request->input("field"),
rank: $request->input("rank") ?? "",
);
} catch (LucentException $th) {
return fail($th);
}
return ok([
"edge" => $edge,
]);
}
//
// public function create(Request $request): Response
// {
// try {
// $edge = EdgeService::create(
// source: $request->input("source"),
// target: $request->input("target"),
// sourceSchema: $request->input("sourceSchema"),
// targetSchema: $request->input("targetSchema"),
// field: $request->input("field"),
// rank: $request->input("rank") ?? "",
// );
// } catch (LucentException $th) {
// return fail($th);
// }
//
//
// return ok([
// "edge" => $edge,
// ]);
// }
}
+43 -46
View File
@@ -2,13 +2,11 @@
namespace Lucent\Http\Controller\Api;
use App\Http\Controllers\Controller;
use Lucent\Http\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
use Lucent\Account\AuthService;
use Lucent\Channel\ChannelRepo;
use Lucent\File\FileUploadResult;
use Lucent\Query\Query;
use Lucent\Record\RecordService;
use function Lucent\File\uploadFile;
use function Lucent\Response\fail;
@@ -16,47 +14,46 @@ use function Lucent\Response\ok;
class FileController extends Controller
{
public function __construct(
private readonly RecordService $recordService,
private readonly Query $query
)
{
}
public function upload(Request $request)
{
$validator = Validator::make(request()->all(), [
'files.*' => 'required|file|max:100000',
]);
if ($validator->fails()) {
return fail($validator->errors()->first());
}
$channel = ChannelRepo::current();
$schema = $channel->schemas->firstWhere("name", $request->input("schema"));
$files = request()->file('files');
$uploadResults = collect($files)->map(fn($file) => uploadFile($schema, $file))->toArray();
$insertedIds = collect($uploadResults)
->filter(fn(FileUploadResult $res) => !$res->isDuplicate)
->values()
->map(function (FileUploadResult $uploadResult) use ($schema, $request) {
return $this->recordService->create(
userId: AuthService::currentUserId($request),
schemaName: $schema->name,
data: [],
file: (array)$uploadResult->recordFile,
edges: [],
status: $request->input("status") ?? "published",
uploadFromUrl: ""
);
})->toArray();
return ok($insertedIds);
}
// public function __construct(
// private readonly RecordService $recordService,
// )
// {
// }
//
// public function upload(Request $request)
// {
// $validator = Validator::make(request()->all(), [
// 'files.*' => 'required|file|max:100000',
// ]);
//
// if ($validator->fails()) {
// return fail($validator->errors()->first());
// }
// $channel = ChannelRepo::current();
// $schema = $channel->schemas->firstWhere("name", $request->input("schema"));
// $files = request()->file('files');
//
//
// $uploadResults = collect($files)->map(fn($file) => uploadFile($schema, $file))->toArray();
// $insertedIds = collect($uploadResults)
// ->filter(fn(FileUploadResult $res) => !$res->isDuplicate)
// ->values()
// ->map(function (FileUploadResult $uploadResult) use ($schema, $request) {
//
//
// return $this->recordService->create(
// userId: AuthService::currentUserId($request),
// schemaName: $schema->name,
// data: [],
// file: (array)$uploadResult->recordFile,
// edges: [],
// status: $request->input("status") ?? "published",
// uploadFromUrl: ""
// );
//
// })->toArray();
//
//
// return ok($insertedIds);
// }
}
+89 -89
View File
@@ -2,7 +2,7 @@
namespace Lucent\Http\Controller\Api;
use App\Http\Controllers\Controller;
use Lucent\Http\Controller;
use Illuminate\Http\Request;
use Lucent\Channel\ChannelRepo;
use Lucent\LucentException;
@@ -15,93 +15,93 @@ use function Lucent\Response\ok;
class RecordController extends Controller
{
public function __construct(
private readonly RecordService $recordService,
private readonly Query $query
)
{
}
public function records(Request $request)
{
$channel = ChannelRepo::current();
$urlParams = $request->all();
$sort = data_get($urlParams, "sort") ?? "-_sys.updatedAt";
$filter = data_get($urlParams, "filter") ?? [];
$arguments = array_merge([
], $filter);
$skip = data_get($urlParams, "skip") ?? 0;
$limit = data_get($urlParams, "limit") ?? 15;
$queryResult = $this->query
->filter($arguments)
->limit($limit)
->skip($skip)
->sort($sort)
->childrenDepth($request->input("childrenDepth") ?? 1)
->parentsDepth($request->input("parentsDepth") ?? 0)
->runWithCount();
$graph = $queryResult->getQueryRecords($channel->schemas);
$total = $queryResult->getTotal();
return ok([
"graph" => $graph->toArray(),
"sort" => $sort,
"limit" => $limit,
"skip" => $skip,
"total" => $total,
]);
}
public function create(Request $request)
{
try {
$recordId = $this->recordService->create(
userId: $request->input("userId"),
schemaName: $request->input("schema"),
data: $request->input("data") ?? [],
file: $request->input("file") ?? [],
edges: $request->input("edges") ?? [],
status: $request->input("status") ?? "draft",
uploadFromUrl: $request->input("uploadFromUrl") ?? ""
);
} catch (ValidatorException $th) {
return fail($th->getValidatorErrors());
} catch (LucentException $th) {
return fail($th);
}
return ok(["id" => $recordId]);
}
public function update(Request $request)
{
try {
$this->recordService->update(
userId: $request->input("userId"),
id: $request->route("id"),
data: $request->input("data"),
status: $request->input("status"),
edges: $request->input("edges") ?? [],
updateEdges: false,
);
} catch (ValidatorException $th) {
return fail($th->getValidatorErrors());
} catch (LucentException $th) {
return fail($th);
} catch (Throwable $th) {
if ($th->getCode() == 11000) {
return fail("ID has to be unique in the channel");
}
return fail($th);
}
return ok();
}
// public function __construct(
// private readonly RecordService $recordService,
// private readonly Query $query
// )
// {
// }
//
// public function records(Request $request)
// {
// $channel = ChannelRepo::current();
// $urlParams = $request->all();
// $sort = data_get($urlParams, "sort") ?? "-_sys.updatedAt";
// $filter = data_get($urlParams, "filter") ?? [];
// $arguments = array_merge([
//
// ], $filter);
//
// $skip = data_get($urlParams, "skip") ?? 0;
// $limit = data_get($urlParams, "limit") ?? 15;
// $queryResult = $this->query
// ->filter($arguments)
// ->limit($limit)
// ->skip($skip)
// ->sort($sort)
// ->childrenDepth($request->input("childrenDepth") ?? 1)
// ->parentsDepth($request->input("parentsDepth") ?? 0)
// ->runWithCount();
//
// $graph = $queryResult->getQueryRecords($channel->schemas);
// $total = $queryResult->getTotal();
//
// return ok([
// "graph" => $graph->toArray(),
// "sort" => $sort,
// "limit" => $limit,
// "skip" => $skip,
// "total" => $total,
// ]);
// }
//
// public function create(Request $request)
// {
//
// try {
//
// $recordId = $this->recordService->create(
// userId: $request->input("userId"),
// schemaName: $request->input("schema"),
// data: $request->input("data") ?? [],
// file: $request->input("file") ?? [],
// edges: $request->input("edges") ?? [],
// status: $request->input("status") ?? "draft",
// uploadFromUrl: $request->input("uploadFromUrl") ?? ""
// );
//
// } catch (ValidatorException $th) {
// return fail($th->getValidatorErrors());
// } catch (LucentException $th) {
// return fail($th);
// }
// return ok(["id" => $recordId]);
// }
//
// public function update(Request $request)
// {
//
// try {
// $this->recordService->update(
// userId: $request->input("userId"),
// id: $request->route("id"),
// data: $request->input("data"),
// status: $request->input("status"),
// edges: $request->input("edges") ?? [],
// updateEdges: false,
// );
// } catch (ValidatorException $th) {
// return fail($th->getValidatorErrors());
// } catch (LucentException $th) {
// return fail($th);
// } catch (Throwable $th) {
// if ($th->getCode() == 11000) {
// return fail("ID has to be unique in the channel");
// }
// return fail($th);
// }
//
// return ok();
// }
}
+1 -1
View File
@@ -2,7 +2,7 @@
namespace Lucent\Http\Controller\Api;
use App\Http\Controllers\Controller;
use Lucent\Http\Controller;
use Illuminate\Http\Request;
use Lucent\Schema\SchemaService;
use function Lucent\Response\fail;
+1 -1
View File
@@ -2,7 +2,7 @@
namespace Lucent\Http\Controller;
use App\Http\Controllers\Controller;
use Lucent\Http\Controller;
use Illuminate\Contracts\Session\Session;
use Illuminate\Contracts\View\View;
use Illuminate\Http\RedirectResponse;
+1 -5
View File
@@ -2,14 +2,10 @@
namespace Lucent\Http\Controller;
use App\Http\Controllers\Controller;
use Lucent\Http\Controller;
use Illuminate\Contracts\View\View;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Artisan;
use Lucent\Channel\ChannelService;
use Lucent\Svelte\Svelte;
use function Lucent\Response\ok;
class BuildController extends Controller
{
+1 -1
View File
@@ -2,7 +2,7 @@
namespace Lucent\Http\Controller;
use App\Http\Controllers\Controller;
use Lucent\Http\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
use Lucent\Channel\ChannelService;
+1 -1
View File
@@ -2,7 +2,7 @@
namespace Lucent\Http\Controller;
use App\Http\Controllers\Controller;
use Lucent\Http\Controller;
use Illuminate\Contracts\View\View;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
+1 -1
View File
@@ -2,7 +2,7 @@
namespace Lucent\Http\Controller;
use App\Http\Controllers\Controller;
use Lucent\Http\Controller;
use Illuminate\Http\Request;
use Lucent\Account\AccountService;
use Lucent\Account\AuthService;
+2 -2
View File
@@ -2,7 +2,7 @@
namespace Lucent\Http\Controller;
use App\Http\Controllers\Controller;
use Lucent\Http\Controller;
use Illuminate\Http\Request;
use Lucent\Account\AccountService;
use Lucent\Account\AuthService;
@@ -203,7 +203,6 @@ class RecordController extends Controller
{
$rid = $request->route("rid");
$graph = $this->query
->filter(["id" => $rid])
->limit(1)
@@ -214,6 +213,7 @@ class RecordController extends Controller
->parentsLimit(200)
->run();
if ($graph->records->isEmpty()) {
return $this->svelte->render(
layout: "channel",
+1 -4
View File
@@ -2,12 +2,9 @@
namespace Lucent\Http\Controller;
use App\Http\Controllers\Controller;
use Lucent\Http\Controller;
use Illuminate\Http\Request;
use Lucent\Record\RecordRepo;
use Lucent\Revision\RevisionRepo;
use Lucent\Revision\RevisionService;
use function Lucent\Response\fail;
use function Lucent\Response\ok;
class RevisionController extends Controller
+10 -2
View File
@@ -2,13 +2,21 @@
namespace Lucent\Query\DatabaseGraph;
use Lucent\Edge\Edge;
use Lucent\Query\QueryOptions;
use Lucent\Support\Collection;
interface DatabaseGraph
{
/**
* @param array<string> $ids
* @return Collection<Edge>
*/
public function getChildren(array $ids, QueryOptions $options): array;
public function getParents(array $ids, QueryOptions $options): array;
public function getChildren(array $ids, QueryOptions $options): Collection;
/**
* @param array<string> $ids
* @return Collection<Edge>
*/
public function getParents(array $ids, QueryOptions $options): Collection;
}
+10 -6
View File
@@ -3,15 +3,18 @@
namespace Lucent\Query\DatabaseGraph;
use Illuminate\Support\Facades\DB;
use Lucent\Edge\Edge;
use Lucent\Query\QueryOptions;
use Lucent\Support\Collection;
class PgsqlDatabaseGraph implements DatabaseGraph
{
/**
* @param array<string> $ids
* @return Collection<Edge>
*/
public function getChildren(array $ids, QueryOptions $options): array
public function getChildren(array $ids, QueryOptions $options): Collection
{
$subquery = DB::table('edges AS g')
->select(DB::raw('g.source,g.target,g.rank,"g"."sourceSchema","g"."targetSchema",g.field, 1 as depth '))
@@ -30,16 +33,17 @@ class PgsqlDatabaseGraph implements DatabaseGraph
->orderBy("rank")
);
return DB::table('search_graph')
return new Collection(DB::table('search_graph')
// ->select(DB::raw("*, 1 as depth "))
->withRecursiveExpression('search_graph', $subquery)
->get()->toArray();
->get()->map([Edge::class, 'fromDB']));
}
/**
* @param array<string> $ids
* @return Collection<Edge>
*/
public function getParents(array $ids, QueryOptions $options): array
public function getParents(array $ids, QueryOptions $options): Collection
{
$subquery = DB::table('edges AS g')
->select(DB::raw('g.source,g.target,g.rank,"g"."sourceSchema","g"."targetSchema",g.field, 1 as depth '))
@@ -59,9 +63,9 @@ class PgsqlDatabaseGraph implements DatabaseGraph
->orderBy("rank")
);
return DB::table('search_graph')
return new Collection(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();
->get()->map([Edge::class, 'fromDB']));
}
}
@@ -3,14 +3,17 @@
namespace Lucent\Query\DatabaseGraph;
use Illuminate\Support\Facades\DB;
use Lucent\Edge\Edge;
use Lucent\Query\QueryOptions;
use Lucent\Support\Collection;
class SqliteDatabaseGraph implements DatabaseGraph
{
/**
* @param array<string> $ids
* @return Collection<Edge>
*/
public function getChildren(array $ids, QueryOptions $options): array
public function getChildren(array $ids, QueryOptions $options): Collection
{
$subquery = DB::table('edges AS g')
->select(DB::raw('g.source,g.target,g.rank,g.sourceSchema,g.targetSchema,g.field, 1 as depth '))
@@ -29,16 +32,17 @@ class SqliteDatabaseGraph implements DatabaseGraph
->orderBy("rank")
);
return DB::table('search_graph')
return new Collection(DB::table('search_graph')
// ->select(DB::raw("*, 1 as depth "))
->withRecursiveExpression('search_graph', $subquery)
->get()->toArray();
->get()->map([Edge::class, 'fromDB']));
}
/**
* @param array<string> $ids
* @return Collection<Edge>
*/
public function getParents(array $ids, QueryOptions $options): array
public function getParents(array $ids, QueryOptions $options): Collection
{
$subquery = DB::table('edges AS g')
->select(DB::raw('g.source,g.target,g.rank,"g"."sourceSchema","g"."targetSchema",g.field, 1 as depth '))
@@ -58,9 +62,9 @@ class SqliteDatabaseGraph implements DatabaseGraph
->orderBy("rank")
);
return DB::table('search_graph')
return new Collection(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();
->get()->map([Edge::class, 'fromDB']));
}
}
+50 -47
View File
@@ -4,23 +4,29 @@ namespace Lucent\Query;
use Lucent\Edge\Edge;
use Lucent\Record\QueryRecord;
use Lucent\Record\Record;
use Lucent\Support\Collection;
use PhpOption\Option;
final class Graph
{
/**
* @param Collection<QueryRecord> $records
* @param Collection<Record> $rootRecords
* @param Collection<Record> $records
* @param Collection<Edge> $edges
* */
public function __construct(
public Collection $records,
public Collection $edges,
public Collection $parentEdges,
public Collection $rootRecords,
public Collection $records,
public Collection $edges,
public Collection $parentEdges,
public QueryOptions $queryOptions,
public ?int $total = null,
public ?int $total = null,
)
{
$this->edges->sortBy("rank")->values();
$this->parentEdges->sortBy("rank")->values();
}
/**
@@ -39,7 +45,8 @@ final class Graph
public function tree(): Collection
{
return $this->getRootRecords()
return $this->rootRecords
->map([QueryRecord::class, 'fromRecord'])
->map(fn($r) => $this->findParents($r))
->map(fn($r) => $this->findChildren($r));
@@ -47,61 +54,57 @@ final class Graph
public function findChildren(QueryRecord $record, int $depth = 1): QueryRecord
{
if($this->queryOptions->childrenDepth < $depth){
if ($this->queryOptions->childrenDepth < $depth) {
return $record;
}
$recordEdges = $this->edges
->filter(fn(Edge $ed) => $ed->source === $record->id )
$record->_children = $this->edges
->filter(fn(Edge $ed) => $ed->source === $record->record->id)
->unique(fn(Edge $ed) => $ed->targetSchema . $ed->field . $ed->target . $ed->source)
->sort(fn($a, $b) => $a->rank <=> $b->rank)->values();
$groupRecordEdges = [];
foreach ($recordEdges as $element) {
$groupRecordEdges[$element->field][] = $element;
}
$children = [];
foreach ($groupRecordEdges as $field => $edges) {
$children[$field] = [];
foreach ($edges as $anEdge) {
$aRecord = $this->records->filter(fn(QueryRecord $rec) => $rec->id == $anEdge->target)->values();
if (empty($aRecord[0])) {
continue;
->sort(fn($a, $b) => $a->rank <=> $b->rank)
->values()
->map(function (Edge $edge): Option {
$records = $this->records->filter(fn(Record $rec) => $rec->id == $edge->target)->values();
if ($records->isEmpty()) {
return none();
}
$queryRecord = QueryRecord::fromRecord($records->first());
$queryRecord->edge = some($edge);
return some($queryRecord);
})
->filter(fn(Option $o) => $o->isDefined())
->values()
->map(fn(Option $o) => $this->findChildren($o->get(), $depth + 1))
->groupBy(fn(QueryRecord $qr) => $qr->edge->get()->field);
$children[$field][] = $this->findChildren($aRecord[0], $depth + 1);
}
}
$record->_children = $children;
return $record;
}
public function findParents(QueryRecord $record, int $depth = 1): QueryRecord
{
if($this->queryOptions->parentsDepth < $depth){
if ($this->queryOptions->parentsDepth < $depth) {
return $record;
}
$recordEdges = $this->parentEdges->filter(fn(Edge $ed) => $ed->target === $record->id)->where("depth", $depth)->values()->sort(fn($a, $b) => $a->rank <=> $b->rank)->values();
$groupRecordEdges = [];
foreach ($recordEdges as $element) {
$groupRecordEdges[$element->field][] = $element;
}
$parents = [];
foreach ($groupRecordEdges as $field => $edges) {
$parents[$field] = [];
foreach ($edges as $anEdge) {
$aRecord = $this->records->filter(fn(QueryRecord $rec) => $rec->id == $anEdge->source)->values();
if (empty($aRecord[0])) {
continue;
$record->_parents = $this->parentEdges
->filter(fn(Edge $ed) => $ed->target === $record->record->id)
->where("depth", $depth)
->values()
->sort(fn($a, $b) => $a->rank <=> $b->rank)
->values()
->map(function (Edge $edge): Option {
$records = $this->records->filter(fn(Record $rec) => $rec->id == $edge->source)->values();
if ($records->isEmpty()) {
return none();
}
$queryRecord = QueryRecord::fromRecord($records->first());
$queryRecord->edge = some($edge);
return some($queryRecord);
})
->filter(fn(Option $o) => $o->isDefined())
->values()
->map(fn(Option $o) => $this->findParents($o->get(), $depth + 1))
->groupBy(fn(QueryRecord $qr) => $qr->edge->get()->field);
$parents[$field][] = $this->findParents($aRecord[0], $depth + 1);
}
}
$record->_parents = $parents;
return $record;
}
}
+29 -80
View File
@@ -4,12 +4,11 @@ namespace Lucent\Query;
use Illuminate\Database\Query\Builder;
use Illuminate\Support\Facades\DB;
use Lucent\Edge\Edge;
use Lucent\Query\DatabaseGraph\DatabaseGraph;
use Lucent\Query\Filter\AndFilter;
use Lucent\Query\Filter\OrFilter;
use Lucent\Record\InputFormatter;
use Lucent\Record\QueryRecord;
use Lucent\Record\Mapper;
use Lucent\Record\Record;
use Lucent\Support\Collection;
@@ -26,6 +25,7 @@ final class Query
public readonly FilterParser $filterParser,
public readonly InputFormatter $inputFormatter,
public readonly DatabaseGraph $databaseGraph,
public readonly Mapper $recordMapper,
)
{
$this->options = new QueryOptions();
@@ -46,91 +46,54 @@ final class Query
public function run(): Graph
{
$resultsRecords = $this->mainQuery();
$ids = array_map(function ($rec) {
return $rec->id;
}, $resultsRecords);
$rootRecords = $this->mainQuery();
$ids = $rootRecords->pluck("id");
$resultChildrenEdgesTargetIds = [];
$resultChildrenEdges = [];
if ($this->options->childrenDepth > 0 && !empty($ids)) {
$resultChildrenEdges = $this->getChildren($ids);
$resultChildrenEdgesTargetIds = array_map(fn($e) => $e->target, $resultChildrenEdges);
$resultChildrenEdges = new Collection();
if ($this->options->childrenDepth > 0 && $ids->isNotEmpty()) {
$resultChildrenEdges = $this->databaseGraph->getChildren($ids->toArray(), $this->options);
$resultChildrenEdgesTargetIds = $resultChildrenEdges->pluck("target");
}
$resultParentSourceTargetIds = [];
$resultParentEdges = [];
if ($this->options->parentsDepth > 0 && !empty($ids)) {
$resultParentEdges = $this->getParents($ids);
$resultParentSourceTargetIds = array_map(fn($e) => $e->source, $resultParentEdges);
$resultParentEdges = new Collection();
if ($this->options->parentsDepth > 0 && $ids->isNotEmpty()) {
$resultParentEdges = $this->databaseGraph->getParents($ids->toArray(), $this->options);
$resultParentSourceTargetIds = $resultParentEdges->pluck("source");
}
$edgesIds = collect($resultParentSourceTargetIds)->merge($resultChildrenEdgesTargetIds)->unique()->values()->toArray();
$edgeRecords = [];
$edgeRecords = new Collection();
if (!empty($edgesIds)) {
$edgeRecords = DB::table('records')
$edgeRecords = new Collection(DB::table('records')
->whereIn("id", $edgesIds)
->whereIn("status", $this->options->status)
->get()->toArray();
->get()->map([$this->recordMapper, 'fromDB']));
}
$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();
$formattedRecords = $this->formatRecords($resultsRecordsUnique, $resultChildrenEdges, $resultParentEdges);
$graph = new Graph(
$rootRecords,
$edgeRecords,
$resultChildrenEdges,
$resultParentEdges,
$this->options
);
$this->reset();
return $formattedRecords;
return $graph;
}
private function reset()
private function reset(): void
{
$this->options = new QueryOptions();
$this->filters = [];
}
private function formatRecords(array $records, array $edges, array $parentEdges): Graph
{
$queryRecords = collect($records)->map(function ($recordData) {
$record = Record::fromDB($recordData);
$record->data = $this->inputFormatter->fill($record->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();
$queryParentEdges = collect($parentEdges)->map(function ($edgeData) {
return Edge::fromArray((array)$edgeData);
})->sortBy("rank")->values()->toArray();
return new Graph(
new Collection($queryRecords),
new Collection($queryEdges),
new Collection($queryParentEdges),
$this->options,
);
}
public function tree(): Collection
{
return $this->run()->tree();
}
private function parseFilters(Builder $query): Builder
{
foreach ($this->filters as $filter) {
@@ -140,8 +103,10 @@ final class Query
return $query;
}
private function mainQuery(): array
/**
* @return Collection<Record>
*/
private function mainQuery(): Collection
{
$query = DB::table("records");
$query = $this->parseFilters($query);
@@ -149,24 +114,8 @@ final class 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
{
return $this->databaseGraph->getChildren($ids, $this->options);
}
private function getParents(array $ids): array
{
return $this->databaseGraph->getParents($ids, $this->options);
return new Collection($query->get()->map([$this->recordMapper, 'fromDB']));
}
+59
View File
@@ -0,0 +1,59 @@
<?php
namespace Lucent\Record;
use Illuminate\Support\Str;
use stdClass;
class Document implements Record
{
function __construct(
public string $id,
public string $schema,
public Status $status,
public System $_sys,
public RecordData $data,
)
{
}
private function indexValues(array $arrObject): string
{
return trim(Str::lower(collect($arrObject)
->map(function ($value) {
if (is_array($value)) {
return $this->indexValues($value ?? []);
}
return str_replace(array("\r", "\n"), '', strip_tags((string)$value));
})
->values()->join(" ")));
}
public function toDB(): array
{
$searchIndex = $this->indexValues($this->data->toArray());
return [
"id" => $this->id,
"status" => $this->status->value,
"schema" => $this->schema,
"_sys" => json_encode($this->_sys),
"_file" => null,
"data" => json_encode($this->data),
"search" => $searchIndex,
];
}
public static function fromDB(stdClass $data): Document
{
return new Document(
id: $data->id,
schema: $data->schema,
status: Status::from($data->status),
_sys: System::fromArray(json_decode($data->_sys, true)),
data: new RecordData(json_decode($data->data, true)),
);
}
}
+45 -22
View File
@@ -2,37 +2,60 @@
namespace Lucent\Record;
use Illuminate\Support\Str;
use stdClass;
class File
class File implements Record
{
function __construct(
public readonly string $originalName,
public readonly string $mime,
public readonly string $path,
public readonly int $size,
public readonly int $width,
public readonly int $height,
public readonly string $checksum,
public string $id,
public string $schema,
public Status $status,
public System $_sys,
public RecordData $data,
public FileInfo $_file,
)
{
}
public static function fromArray(array $data): File
{
return new File(
originalName: data_get($data, "originalName"),
mime: data_get($data, "mime"),
path: data_get($data, "path"),
size: data_get($data, "size"),
width: data_get($data, "width"),
height: data_get($data, "height"),
checksum: data_get($data, "checksum"),
);
private function indexValues(array $arrObject){
return trim(Str::lower(collect($arrObject)
->map(function($value){
if(is_array($value)){
return $this->indexValues($value ?? []);
}
return str_replace(array("\r", "\n"), '', strip_tags((string)$value));
})
->values()->join(" ")." ". $this->_file->originalName));
}
public function toArray(): array
public function toDB(): array
{
return \json_decode(\json_encode($this), true);
$searchIndex = $this->indexValues($this->data->toArray());
return [
"id" => $this->id,
"status" => $this->status->value,
"schema" => $this->schema,
"_sys" => json_encode($this->_sys),
"_file" => json_encode($this->_file),
"data" => json_encode($this->data),
"search" => $searchIndex,
];
}
}
public static function fromDB(stdClass $data): File
{
return new File(
id: $data->id,
schema: $data->schema,
status: Status::from($data->status),
_sys: System::fromArray(json_decode($data->_sys, true)),
data: new RecordData(json_decode($data->data, true)),
_file: FileInfo::fromJSON($data->_file),
);
}
}
+44
View File
@@ -0,0 +1,44 @@
<?php
namespace Lucent\Record;
readonly class FileInfo
{
function __construct(
public string $originalName,
public string $mime,
public string $path,
public int $size,
public int $width,
public int $height,
public string $checksum,
)
{
}
public static function fromArray(array $data): FileInfo
{
return new FileInfo(
originalName: data_get($data, "originalName"),
mime: data_get($data, "mime"),
path: data_get($data, "path"),
size: data_get($data, "size"),
width: data_get($data, "width"),
height: data_get($data, "height"),
checksum: data_get($data, "checksum"),
);
}
public function toArray(): array
{
return \json_decode(\json_encode($this), true);
}
public static function fromJSON(string $json): self
{
$file = json_decode($json, true);
return self::fromArray($file);
}
}
+25
View File
@@ -0,0 +1,25 @@
<?php
namespace Lucent\Record;
use stdClass;
class Mapper
{
public function __construct(
public InputFormatter $inputFormatter
)
{
}
public function fromDB(stdClass $data): Record
{
$record = match (true) {
!empty($data->_file) => File::fromDB($data),
default => Document::fromDB($data),
};
$record->data = $this->inputFormatter->fill($record->schema, $record->data);
return $record;
}
}
+15 -18
View File
@@ -2,21 +2,23 @@
namespace Lucent\Record;
use Lucent\LucentException;
use Lucent\Edge\Edge;
use Lucent\Support\Collection;
use PhpOption\Option;
class QueryRecord
{
/**
* @param Record $record
* @param Option<Edge> $edge
* @param Collection<QueryRecord> $_children
* @param Collection<QueryRecord> $_parents
*/
function __construct(
public string $id,
public string $schema,
public Status $status,
public System $_sys,
public RecordData $data,
public bool $isRoot,
public ?File $_file = null,
public array $_children = [],
public array $_parents = [],
public Record $record,
public Option $edge,
public Collection $_children = new Collection(),
public Collection $_parents = new Collection(),
)
{
}
@@ -24,13 +26,8 @@ class QueryRecord
public static function fromRecord(Record $record): QueryRecord
{
return new QueryRecord(
id: $record->id,
schema: $record->schema,
status: $record->status,
_sys: $record->_sys,
data: $record->data,
isRoot: false,
_file: $record->_file,
record: $record,
edge: none(),
);
}
}
+4 -67
View File
@@ -2,78 +2,15 @@
namespace Lucent\Record;
use Illuminate\Support\Str;
use JsonSerializable;
use stdClass;
use Illuminate\Support\Str;
class Record implements JsonSerializable
interface Record
{
function __construct(
public string $id,
public string $schema,
public Status $status,
public System $_sys,
public RecordData $data,
public ?File $_file = null,
)
{
}
private function indexValues(array $arrObject){
return trim(Str::lower(collect($arrObject)
->map(function($value){
if(is_array($value)){
return $this->indexValues($value ?? []);
}
return str_replace(array("\r", "\n"), '', strip_tags((string)$value));
})
->values()->join(" ")." ". $this->_file?->originalName ?? ""));
}
public function toDB(): array
{
$searchIndex = $this->indexValues($this->data->toArray());
return [
"id" => $this->id,
"status" => $this->status->value,
"schema" => $this->schema,
"_sys" => json_encode($this->_sys),
"_file" => json_encode($this->_file),
"data" => json_encode($this->data),
"search" => $searchIndex,
];
}
public static function fromDB(stdClass $data): Record
{
$file = json_decode($data->_file, true);
if (!empty($file)) {
$file = new File(...$file);
} else {
$file = null;
}
return new Record(
id: $data->id,
schema: $data->schema,
status: Status::from($data->status),
_sys: System::fromArray(json_decode($data->_sys, true)),
data: new RecordData(json_decode($data->data, true)),
_file: $file,
);
}
public function jsonSerialize(): static
{
return $this;
}
static function fromDB(stdClass $data): Record;
public function toDB(): array;
}
+2 -2
View File
@@ -4,7 +4,7 @@ namespace Lucent\Revision;
use Illuminate\Support\Str;
use Lucent\Edge\EdgeCollection;
use Lucent\Record\File;
use Lucent\Record\FileInfo;
use Lucent\Record\Record;
use Lucent\Record\RecordData;
use Lucent\Record\System;
@@ -19,7 +19,7 @@ readonly class Revision
public System $_sys,
public RecordData $data,
public EdgeCollection $_edges,
public ?File $_file = null,
public ?FileInfo $_file = null,
)
{
+2 -2
View File
@@ -5,7 +5,7 @@ namespace Lucent\Revision;
use Illuminate\Support\Facades\DB;
use Lucent\Edge\Edge;
use Lucent\Edge\EdgeCollection;
use Lucent\Record\File;
use Lucent\Record\FileInfo;
use Lucent\Record\RecordData;
use Lucent\Record\System;
use Lucent\Support\Collection;
@@ -91,7 +91,7 @@ class RevisionRepo
$file = json_decode($data->_file, true);
if (!empty($file)) {
$file = new File(...$file);
$file = new FileInfo(...$file);
} else {
$file = null;
}