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\Chapter;
|
||||||
use BookStack\Entities\Models\Entity;
|
use BookStack\Entities\Models\Entity;
|
||||||
use BookStack\Entities\Models\Page;
|
use BookStack\Entities\Models\Page;
|
||||||
|
use BookStack\Entities\Tools\MixedEntityListLoader;
|
||||||
use BookStack\Permissions\PermissionApplicator;
|
use BookStack\Permissions\PermissionApplicator;
|
||||||
use BookStack\Users\Models\User;
|
use BookStack\Users\Models\User;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
@ -14,11 +15,10 @@ use Illuminate\Database\Eloquent\Relations\Relation;
|
|||||||
|
|
||||||
class ActivityQueries
|
class ActivityQueries
|
||||||
{
|
{
|
||||||
protected PermissionApplicator $permissions;
|
public function __construct(
|
||||||
|
protected PermissionApplicator $permissions,
|
||||||
public function __construct(PermissionApplicator $permissions)
|
protected MixedEntityListLoader $listLoader,
|
||||||
{
|
) {
|
||||||
$this->permissions = $permissions;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -29,11 +29,13 @@ class ActivityQueries
|
|||||||
$activityList = $this->permissions
|
$activityList = $this->permissions
|
||||||
->restrictEntityRelationQuery(Activity::query(), 'activities', 'entity_id', 'entity_type')
|
->restrictEntityRelationQuery(Activity::query(), 'activities', 'entity_id', 'entity_type')
|
||||||
->orderBy('created_at', 'desc')
|
->orderBy('created_at', 'desc')
|
||||||
->with(['user', 'entity'])
|
->with(['user'])
|
||||||
->skip($count * $page)
|
->skip($count * $page)
|
||||||
->take($count)
|
->take($count)
|
||||||
->get();
|
->get();
|
||||||
|
|
||||||
|
$this->listLoader->loadIntoRelations($activityList->all(), 'entity', false);
|
||||||
|
|
||||||
return $this->filterSimilar($activityList);
|
return $this->filterSimilar($activityList);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ namespace BookStack\App;
|
|||||||
use BookStack\Activity\ActivityQueries;
|
use BookStack\Activity\ActivityQueries;
|
||||||
use BookStack\Entities\Models\Book;
|
use BookStack\Entities\Models\Book;
|
||||||
use BookStack\Entities\Models\Page;
|
use BookStack\Entities\Models\Page;
|
||||||
|
use BookStack\Entities\Queries\PageQueries;
|
||||||
use BookStack\Entities\Queries\RecentlyViewed;
|
use BookStack\Entities\Queries\RecentlyViewed;
|
||||||
use BookStack\Entities\Queries\TopFavourites;
|
use BookStack\Entities\Queries\TopFavourites;
|
||||||
use BookStack\Entities\Repos\BookRepo;
|
use BookStack\Entities\Repos\BookRepo;
|
||||||
@ -26,9 +27,7 @@ class HomeController extends Controller
|
|||||||
$draftPages = [];
|
$draftPages = [];
|
||||||
|
|
||||||
if ($this->isSignedIn()) {
|
if ($this->isSignedIn()) {
|
||||||
$draftPages = Page::visible()
|
$draftPages = PageQueries::currentUserDraftsForList()
|
||||||
->where('draft', '=', true)
|
|
||||||
->where('created_by', '=', user()->id)
|
|
||||||
->orderBy('updated_at', 'desc')
|
->orderBy('updated_at', 'desc')
|
||||||
->with('book')
|
->with('book')
|
||||||
->take(6)
|
->take(6)
|
||||||
@ -40,11 +39,10 @@ class HomeController extends Controller
|
|||||||
(new RecentlyViewed())->run(12 * $recentFactor, 1)
|
(new RecentlyViewed())->run(12 * $recentFactor, 1)
|
||||||
: Book::visible()->orderBy('created_at', 'desc')->take(12 * $recentFactor)->get();
|
: Book::visible()->orderBy('created_at', 'desc')->take(12 * $recentFactor)->get();
|
||||||
$favourites = (new TopFavourites())->run(6);
|
$favourites = (new TopFavourites())->run(6);
|
||||||
$recentlyUpdatedPages = Page::visible()->with('book')
|
$recentlyUpdatedPages = PageQueries::visibleForList()
|
||||||
->where('draft', false)
|
->where('draft', false)
|
||||||
->orderBy('updated_at', 'desc')
|
->orderBy('updated_at', 'desc')
|
||||||
->take($favourites->count() > 0 ? 5 : 10)
|
->take($favourites->count() > 0 ? 5 : 10)
|
||||||
->select(Page::$listAttributes)
|
|
||||||
->get();
|
->get();
|
||||||
|
|
||||||
$homepageOptions = ['default', 'books', 'bookshelves', 'page'];
|
$homepageOptions = ['default', 'books', 'bookshelves', 'page'];
|
||||||
@ -95,7 +93,7 @@ class HomeController extends Controller
|
|||||||
$homepageSetting = setting('app-homepage', '0:');
|
$homepageSetting = setting('app-homepage', '0:');
|
||||||
$id = intval(explode(':', $homepageSetting)[0]);
|
$id = intval(explode(':', $homepageSetting)[0]);
|
||||||
/** @var Page $customHomepage */
|
/** @var Page $customHomepage */
|
||||||
$customHomepage = Page::query()->where('draft', '=', false)->findOrFail($id);
|
$customHomepage = PageQueries::start()->where('draft', '=', false)->findOrFail($id);
|
||||||
$pageContent = new PageContent($customHomepage);
|
$pageContent = new PageContent($customHomepage);
|
||||||
$customHomepage->html = $pageContent->render(false);
|
$customHomepage->html = $pageContent->render(false);
|
||||||
|
|
||||||
|
@ -18,20 +18,6 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|||||||
*/
|
*/
|
||||||
abstract class BookChild extends Entity
|
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
|
* Scope a query to find items where the child has the given childSlug
|
||||||
* where its parent has the bookSlug.
|
* where its parent has the bookSlug.
|
||||||
|
@ -3,10 +3,16 @@
|
|||||||
namespace BookStack\Entities\Queries;
|
namespace BookStack\Entities\Queries;
|
||||||
|
|
||||||
use BookStack\Entities\EntityProvider;
|
use BookStack\Entities\EntityProvider;
|
||||||
|
use BookStack\Entities\Tools\MixedEntityListLoader;
|
||||||
use BookStack\Permissions\PermissionApplicator;
|
use BookStack\Permissions\PermissionApplicator;
|
||||||
|
|
||||||
abstract class EntityQuery
|
abstract class EntityQuery
|
||||||
{
|
{
|
||||||
|
protected function mixedEntityListLoader(): MixedEntityListLoader
|
||||||
|
{
|
||||||
|
return app()->make(MixedEntityListLoader::class);
|
||||||
|
}
|
||||||
|
|
||||||
protected function permissionService(): PermissionApplicator
|
protected function permissionService(): PermissionApplicator
|
||||||
{
|
{
|
||||||
return app()->make(PermissionApplicator::class);
|
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
|
public function run(int $count, int $page): Collection
|
||||||
{
|
{
|
||||||
$user = user();
|
$user = user();
|
||||||
if ($user === null || $user->isGuest()) {
|
if ($user->isGuest()) {
|
||||||
return collect();
|
return collect();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -23,11 +23,13 @@ class RecentlyViewed extends EntityQuery
|
|||||||
->orderBy('views.updated_at', 'desc')
|
->orderBy('views.updated_at', 'desc')
|
||||||
->where('user_id', '=', user()->id);
|
->where('user_id', '=', user()->id);
|
||||||
|
|
||||||
return $query->with('viewable')
|
$views = $query
|
||||||
->skip(($page - 1) * $count)
|
->skip(($page - 1) * $count)
|
||||||
->take($count)
|
->take($count)
|
||||||
->get()
|
->get();
|
||||||
->pluck('viewable')
|
|
||||||
->filter();
|
$this->mixedEntityListLoader()->loadIntoRelations($views->all(), 'viewable', false);
|
||||||
|
|
||||||
|
return $views->pluck('viewable')->filter();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,11 +25,13 @@ class TopFavourites extends EntityQuery
|
|||||||
->orderBy('views.views', 'desc')
|
->orderBy('views.views', 'desc')
|
||||||
->where('favourites.user_id', '=', user()->id);
|
->where('favourites.user_id', '=', user()->id);
|
||||||
|
|
||||||
return $query->with('favouritable')
|
$favourites = $query
|
||||||
->skip($skip)
|
->skip($skip)
|
||||||
->take($count)
|
->take($count)
|
||||||
->get()
|
->get();
|
||||||
->pluck('favouritable')
|
|
||||||
->filter();
|
$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'.
|
* This will look for a model id and type via 'name_id' and 'name_type'.
|
||||||
* @param Model[] $relations
|
* @param Model[] $relations
|
||||||
*/
|
*/
|
||||||
public function loadIntoRelations(array $relations, string $relationName): void
|
public function loadIntoRelations(array $relations, string $relationName, bool $loadParents): void
|
||||||
{
|
{
|
||||||
$idsByType = [];
|
$idsByType = [];
|
||||||
foreach ($relations as $relation) {
|
foreach ($relations as $relation) {
|
||||||
@ -40,7 +40,7 @@ class MixedEntityListLoader
|
|||||||
$idsByType[$type][] = $id;
|
$idsByType[$type][] = $id;
|
||||||
}
|
}
|
||||||
|
|
||||||
$modelMap = $this->idsByTypeToModelMap($idsByType);
|
$modelMap = $this->idsByTypeToModelMap($idsByType, $loadParents);
|
||||||
|
|
||||||
foreach ($relations as $relation) {
|
foreach ($relations as $relation) {
|
||||||
$type = $relation->getAttribute($relationName . '_type');
|
$type = $relation->getAttribute($relationName . '_type');
|
||||||
@ -56,7 +56,7 @@ class MixedEntityListLoader
|
|||||||
* @param array<string, int[]> $idsByType
|
* @param array<string, int[]> $idsByType
|
||||||
* @return array<string, array<int, Model>>
|
* @return array<string, array<int, Model>>
|
||||||
*/
|
*/
|
||||||
protected function idsByTypeToModelMap(array $idsByType): array
|
protected function idsByTypeToModelMap(array $idsByType, bool $eagerLoadParents): array
|
||||||
{
|
{
|
||||||
$modelMap = [];
|
$modelMap = [];
|
||||||
|
|
||||||
@ -67,10 +67,10 @@ class MixedEntityListLoader
|
|||||||
|
|
||||||
$instance = $this->entityProvider->get($type);
|
$instance = $this->entityProvider->get($type);
|
||||||
$models = $instance->newQuery()
|
$models = $instance->newQuery()
|
||||||
->select($this->listAttributes[$type])
|
->select(array_merge($this->listAttributes[$type], $this->getSubSelectsForQuery($type)))
|
||||||
->scopes('visible')
|
->scopes('visible')
|
||||||
->whereIn('id', $ids)
|
->whereIn('id', $ids)
|
||||||
->with($this->getRelationsToEagerLoad($type))
|
->with($eagerLoadParents ? $this->getRelationsToEagerLoad($type) : [])
|
||||||
->get();
|
->get();
|
||||||
|
|
||||||
if (count($models) > 0) {
|
if (count($models) > 0) {
|
||||||
@ -100,4 +100,19 @@ class MixedEntityListLoader
|
|||||||
|
|
||||||
return $toLoad;
|
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
|
public function getReferencesToEntity(Entity $entity): Collection
|
||||||
{
|
{
|
||||||
$references = $this->queryReferencesToEntity($entity)->get();
|
$references = $this->queryReferencesToEntity($entity)->get();
|
||||||
$this->mixedEntityListLoader->loadIntoRelations($references->all(), 'from');
|
$this->mixedEntityListLoader->loadIntoRelations($references->all(), 'from', true);
|
||||||
|
|
||||||
return $references;
|
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