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