mirror of
https://github.com/BookStackApp/BookStack.git
synced 2024-10-01 01:36:00 -04:00
DB: Started update of entity loading to avoid global selects
Removes page/chpater addSelect global query, to load book slug, and instead extracts base queries to be managed in new static class, while updating specific entitiy relation loading to use our more efficient MixedEntityListLoader where appropriate. Related to #4823
This commit is contained in:
parent
2460e7c56e
commit
a70ed81908
@ -7,6 +7,7 @@ use BookStack\Entities\Models\Book;
|
||||
use BookStack\Entities\Models\Chapter;
|
||||
use BookStack\Entities\Models\Entity;
|
||||
use BookStack\Entities\Models\Page;
|
||||
use BookStack\Entities\Tools\MixedEntityListLoader;
|
||||
use BookStack\Permissions\PermissionApplicator;
|
||||
use BookStack\Users\Models\User;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
@ -14,11 +15,10 @@ use Illuminate\Database\Eloquent\Relations\Relation;
|
||||
|
||||
class ActivityQueries
|
||||
{
|
||||
protected PermissionApplicator $permissions;
|
||||
|
||||
public function __construct(PermissionApplicator $permissions)
|
||||
{
|
||||
$this->permissions = $permissions;
|
||||
public function __construct(
|
||||
protected PermissionApplicator $permissions,
|
||||
protected MixedEntityListLoader $listLoader,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
@ -29,11 +29,13 @@ class ActivityQueries
|
||||
$activityList = $this->permissions
|
||||
->restrictEntityRelationQuery(Activity::query(), 'activities', 'entity_id', 'entity_type')
|
||||
->orderBy('created_at', 'desc')
|
||||
->with(['user', 'entity'])
|
||||
->with(['user'])
|
||||
->skip($count * $page)
|
||||
->take($count)
|
||||
->get();
|
||||
|
||||
$this->listLoader->loadIntoRelations($activityList->all(), 'entity', false);
|
||||
|
||||
return $this->filterSimilar($activityList);
|
||||
}
|
||||
|
||||
|
@ -5,6 +5,7 @@ namespace BookStack\App;
|
||||
use BookStack\Activity\ActivityQueries;
|
||||
use BookStack\Entities\Models\Book;
|
||||
use BookStack\Entities\Models\Page;
|
||||
use BookStack\Entities\Queries\PageQueries;
|
||||
use BookStack\Entities\Queries\RecentlyViewed;
|
||||
use BookStack\Entities\Queries\TopFavourites;
|
||||
use BookStack\Entities\Repos\BookRepo;
|
||||
@ -26,9 +27,7 @@ class HomeController extends Controller
|
||||
$draftPages = [];
|
||||
|
||||
if ($this->isSignedIn()) {
|
||||
$draftPages = Page::visible()
|
||||
->where('draft', '=', true)
|
||||
->where('created_by', '=', user()->id)
|
||||
$draftPages = PageQueries::currentUserDraftsForList()
|
||||
->orderBy('updated_at', 'desc')
|
||||
->with('book')
|
||||
->take(6)
|
||||
@ -40,11 +39,10 @@ class HomeController extends Controller
|
||||
(new RecentlyViewed())->run(12 * $recentFactor, 1)
|
||||
: Book::visible()->orderBy('created_at', 'desc')->take(12 * $recentFactor)->get();
|
||||
$favourites = (new TopFavourites())->run(6);
|
||||
$recentlyUpdatedPages = Page::visible()->with('book')
|
||||
$recentlyUpdatedPages = PageQueries::visibleForList()
|
||||
->where('draft', false)
|
||||
->orderBy('updated_at', 'desc')
|
||||
->take($favourites->count() > 0 ? 5 : 10)
|
||||
->select(Page::$listAttributes)
|
||||
->get();
|
||||
|
||||
$homepageOptions = ['default', 'books', 'bookshelves', 'page'];
|
||||
@ -95,7 +93,7 @@ class HomeController extends Controller
|
||||
$homepageSetting = setting('app-homepage', '0:');
|
||||
$id = intval(explode(':', $homepageSetting)[0]);
|
||||
/** @var Page $customHomepage */
|
||||
$customHomepage = Page::query()->where('draft', '=', false)->findOrFail($id);
|
||||
$customHomepage = PageQueries::start()->where('draft', '=', false)->findOrFail($id);
|
||||
$pageContent = new PageContent($customHomepage);
|
||||
$customHomepage->html = $pageContent->render(false);
|
||||
|
||||
|
@ -18,20 +18,6 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
*/
|
||||
abstract class BookChild extends Entity
|
||||
{
|
||||
protected static function boot()
|
||||
{
|
||||
parent::boot();
|
||||
|
||||
// Load book slugs onto these models by default during query-time
|
||||
static::addGlobalScope('book_slug', function (Builder $builder) {
|
||||
$builder->addSelect(['book_slug' => function ($builder) {
|
||||
$builder->select('slug')
|
||||
->from('books')
|
||||
->whereColumn('books.id', '=', 'book_id');
|
||||
}]);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope a query to find items where the child has the given childSlug
|
||||
* where its parent has the bookSlug.
|
||||
|
@ -3,10 +3,16 @@
|
||||
namespace BookStack\Entities\Queries;
|
||||
|
||||
use BookStack\Entities\EntityProvider;
|
||||
use BookStack\Entities\Tools\MixedEntityListLoader;
|
||||
use BookStack\Permissions\PermissionApplicator;
|
||||
|
||||
abstract class EntityQuery
|
||||
{
|
||||
protected function mixedEntityListLoader(): MixedEntityListLoader
|
||||
{
|
||||
return app()->make(MixedEntityListLoader::class);
|
||||
}
|
||||
|
||||
protected function permissionService(): PermissionApplicator
|
||||
{
|
||||
return app()->make(PermissionApplicator::class);
|
||||
|
31
app/Entities/Queries/PageQueries.php
Normal file
31
app/Entities/Queries/PageQueries.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Entities\Queries;
|
||||
|
||||
use BookStack\Entities\Models\Page;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class PageQueries
|
||||
{
|
||||
public static function start(): Builder
|
||||
{
|
||||
return Page::query();
|
||||
}
|
||||
|
||||
public static function visibleForList(): Builder
|
||||
{
|
||||
return Page::visible()
|
||||
->select(array_merge(Page::$listAttributes, ['book_slug' => function ($builder) {
|
||||
$builder->select('slug')
|
||||
->from('books')
|
||||
->whereColumn('books.id', '=', 'pages.book_id');
|
||||
}]));
|
||||
}
|
||||
|
||||
public static function currentUserDraftsForList(): Builder
|
||||
{
|
||||
return static::visibleForList()
|
||||
->where('draft', '=', true)
|
||||
->where('created_by', '=', user()->id);
|
||||
}
|
||||
}
|
@ -10,7 +10,7 @@ class RecentlyViewed extends EntityQuery
|
||||
public function run(int $count, int $page): Collection
|
||||
{
|
||||
$user = user();
|
||||
if ($user === null || $user->isGuest()) {
|
||||
if ($user->isGuest()) {
|
||||
return collect();
|
||||
}
|
||||
|
||||
@ -23,11 +23,13 @@ class RecentlyViewed extends EntityQuery
|
||||
->orderBy('views.updated_at', 'desc')
|
||||
->where('user_id', '=', user()->id);
|
||||
|
||||
return $query->with('viewable')
|
||||
$views = $query
|
||||
->skip(($page - 1) * $count)
|
||||
->take($count)
|
||||
->get()
|
||||
->pluck('viewable')
|
||||
->filter();
|
||||
->get();
|
||||
|
||||
$this->mixedEntityListLoader()->loadIntoRelations($views->all(), 'viewable', false);
|
||||
|
||||
return $views->pluck('viewable')->filter();
|
||||
}
|
||||
}
|
||||
|
@ -25,11 +25,13 @@ class TopFavourites extends EntityQuery
|
||||
->orderBy('views.views', 'desc')
|
||||
->where('favourites.user_id', '=', user()->id);
|
||||
|
||||
return $query->with('favouritable')
|
||||
$favourites = $query
|
||||
->skip($skip)
|
||||
->take($count)
|
||||
->get()
|
||||
->pluck('favouritable')
|
||||
->filter();
|
||||
->get();
|
||||
|
||||
$this->mixedEntityListLoader()->loadIntoRelations($favourites->all(), 'favouritable', false);
|
||||
|
||||
return $favourites->pluck('favouritable')->filter();
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ class MixedEntityListLoader
|
||||
* This will look for a model id and type via 'name_id' and 'name_type'.
|
||||
* @param Model[] $relations
|
||||
*/
|
||||
public function loadIntoRelations(array $relations, string $relationName): void
|
||||
public function loadIntoRelations(array $relations, string $relationName, bool $loadParents): void
|
||||
{
|
||||
$idsByType = [];
|
||||
foreach ($relations as $relation) {
|
||||
@ -40,7 +40,7 @@ class MixedEntityListLoader
|
||||
$idsByType[$type][] = $id;
|
||||
}
|
||||
|
||||
$modelMap = $this->idsByTypeToModelMap($idsByType);
|
||||
$modelMap = $this->idsByTypeToModelMap($idsByType, $loadParents);
|
||||
|
||||
foreach ($relations as $relation) {
|
||||
$type = $relation->getAttribute($relationName . '_type');
|
||||
@ -56,7 +56,7 @@ class MixedEntityListLoader
|
||||
* @param array<string, int[]> $idsByType
|
||||
* @return array<string, array<int, Model>>
|
||||
*/
|
||||
protected function idsByTypeToModelMap(array $idsByType): array
|
||||
protected function idsByTypeToModelMap(array $idsByType, bool $eagerLoadParents): array
|
||||
{
|
||||
$modelMap = [];
|
||||
|
||||
@ -67,10 +67,10 @@ class MixedEntityListLoader
|
||||
|
||||
$instance = $this->entityProvider->get($type);
|
||||
$models = $instance->newQuery()
|
||||
->select($this->listAttributes[$type])
|
||||
->select(array_merge($this->listAttributes[$type], $this->getSubSelectsForQuery($type)))
|
||||
->scopes('visible')
|
||||
->whereIn('id', $ids)
|
||||
->with($this->getRelationsToEagerLoad($type))
|
||||
->with($eagerLoadParents ? $this->getRelationsToEagerLoad($type) : [])
|
||||
->get();
|
||||
|
||||
if (count($models) > 0) {
|
||||
@ -100,4 +100,19 @@ class MixedEntityListLoader
|
||||
|
||||
return $toLoad;
|
||||
}
|
||||
|
||||
protected function getSubSelectsForQuery(string $type): array
|
||||
{
|
||||
$subSelects = [];
|
||||
|
||||
if ($type === 'chapter' || $type === 'page') {
|
||||
$subSelects['book_slug'] = function ($builder) {
|
||||
$builder->select('slug')
|
||||
->from('books')
|
||||
->whereColumn('books.id', '=', 'book_id');
|
||||
};
|
||||
}
|
||||
|
||||
return $subSelects;
|
||||
}
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ class ReferenceFetcher
|
||||
public function getReferencesToEntity(Entity $entity): Collection
|
||||
{
|
||||
$references = $this->queryReferencesToEntity($entity)->get();
|
||||
$this->mixedEntityListLoader->loadIntoRelations($references->all(), 'from');
|
||||
$this->mixedEntityListLoader->loadIntoRelations($references->all(), 'from', true);
|
||||
|
||||
return $references;
|
||||
}
|
||||
|
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('views', function (Blueprint $table) {
|
||||
$table->index(['updated_at'], 'views_updated_at_index');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('views', function (Blueprint $table) {
|
||||
$table->dropIndex('views_updated_at_index');
|
||||
});
|
||||
}
|
||||
};
|
Loading…
Reference in New Issue
Block a user