1
0
mirror of https://github.com/flarum/core.git synced 2025-07-30 21:20:24 +02:00

perf(core,mentions): limit mentionedBy post relation results (#3780)

* perf(core,mentions): limit `mentionedBy` post relation results

Signed-off-by: Sami Mazouz <sychocouldy@gmail.com>

* Apply fixes from StyleCI

* chore: use a static property to allow customization

Signed-off-by: Sami Mazouz <sychocouldy@gmail.com>

* chore: use a static property to allow customization

Signed-off-by: Sami Mazouz <sychocouldy@gmail.com>

* chore: include count in show post endpoint

Signed-off-by: Sami Mazouz <sychocouldy@gmail.com>

* chore: consistent locale key format

Signed-off-by: Sami Mazouz <sychocouldy@gmail.com>

* chore: forgot to delete `FilterVisiblePosts`

Signed-off-by: Sami Mazouz <sychocouldy@gmail.com>

* test: `mentionedByCount` must not include invisible posts to actor

Signed-off-by: Sami Mazouz <sychocouldy@gmail.com>

* fix: visibility scoping on `mentionedByCount`

Signed-off-by: Sami Mazouz <sychocouldy@gmail.com>

* fix: `loadAggregates` conflicts with visibility scopers

Signed-off-by: Sami Mazouz <sychocouldy@gmail.com>

* Apply fixes from StyleCI

* chore: phpstan

Signed-off-by: Sami Mazouz <sychocouldy@gmail.com>

---------

Signed-off-by: Sami Mazouz <sychocouldy@gmail.com>
Co-authored-by: StyleCI Bot <bot@styleci.io>
This commit is contained in:
Sami Mazouz
2023-04-19 08:23:08 +01:00
committed by GitHub
parent 13e655aca5
commit fbbece4bda
22 changed files with 552 additions and 141 deletions

View File

@@ -76,6 +76,7 @@
"psr/http-server-handler": "^1.0",
"psr/http-server-middleware": "^1.0",
"s9e/text-formatter": "^2.3.6",
"staudenmeir/eloquent-eager-limit": "^1.0",
"sycho/json-api": "^0.5.0",
"sycho/sourcemap": "^2.0.0",
"symfony/config": "^5.2.2",

View File

@@ -79,6 +79,11 @@
left: auto;
right: 0;
}
.Dropdown-menu--inline {
position: relative;
display: block;
box-shadow: none;
}
.Dropdown-header {
padding: 10px 15px;
color: var(--heading-color);

View File

@@ -353,11 +353,14 @@
word-wrap: break-word;
}
.Avatar {
&-badge, .Avatar {
float: left;
margin-left: -50px;
.Avatar--size(32px);
}
&-badge {
color: var(--control-color);
}
.username {
color: var(--text-color);
font-weight: bold;

View File

@@ -94,7 +94,8 @@ class ShowDiscussionController extends AbstractShowController
$discussion = $this->discussions->findOrFail($discussionId, $actor);
}
if (in_array('posts', $include)) {
// If posts is included or a sub relation of post is included.
if (in_array('posts', $include) || Str::contains(implode(',', $include), 'posts.')) {
$postRelationships = $this->getPostRelationships($include);
$this->includePosts($discussion, $request, $postRelationships);
@@ -119,7 +120,7 @@ class ShowDiscussionController extends AbstractShowController
$offset = $this->getPostsOffset($request, $discussion, $limit);
$allPosts = $this->loadPostIds($discussion, $actor);
$loadedPosts = $this->loadPosts($discussion, $actor, $offset, $limit, $include);
$loadedPosts = $this->loadPosts($discussion, $actor, $offset, $limit, $include, $request);
array_splice($allPosts, $offset, $limit, $loadedPosts);
@@ -136,11 +137,7 @@ class ShowDiscussionController extends AbstractShowController
return $discussion->posts()->whereVisibleTo($actor)->orderBy('number')->pluck('id')->all();
}
/**
* @param array $include
* @return array
*/
private function getPostRelationships(array $include)
private function getPostRelationships(array $include): array
{
$prefixLength = strlen($prefix = 'posts.');
$relationships = [];
@@ -183,11 +180,11 @@ class ShowDiscussionController extends AbstractShowController
* @param array $include
* @return mixed
*/
private function loadPosts($discussion, $actor, $offset, $limit, array $include)
private function loadPosts($discussion, $actor, $offset, $limit, array $include, ServerRequestInterface $request)
{
$query = $discussion->posts()->whereVisibleTo($actor);
$query->orderBy('number')->skip($offset)->take($limit)->with($include);
$query->orderBy('number')->skip($offset)->take($limit);
$posts = $query->get();
@@ -195,7 +192,7 @@ class ShowDiscussionController extends AbstractShowController
$post->discussion = $discussion;
}
$this->loadRelations($posts, $include);
$this->loadRelations($posts, $include, $request);
return $posts->all();
}
@@ -221,8 +218,13 @@ class ShowDiscussionController extends AbstractShowController
$postCallableRelationships = $this->getPostRelationships(array_keys($addedCallableRelations));
return array_intersect_key($addedCallableRelations, array_flip(array_map(function ($relation) {
$relationCallables = array_intersect_key($addedCallableRelations, array_flip(array_map(function ($relation) {
return "posts.$relation";
}, $postCallableRelationships)));
// remove posts. prefix from keys
return array_combine(array_map(function ($relation) {
return substr($relation, 6);
}, array_keys($relationCallables)), array_values($relationCallables));
}
}

View File

@@ -12,6 +12,7 @@ namespace Flarum\Api\Controller;
use Flarum\Api\Serializer\PostSerializer;
use Flarum\Http\RequestUtil;
use Flarum\Post\PostRepository;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Arr;
use Psr\Http\Message\ServerRequestInterface;
use Tobscure\JsonApi\Document;
@@ -52,6 +53,12 @@ class ShowPostController extends AbstractShowController
*/
protected function data(ServerRequestInterface $request, Document $document)
{
return $this->posts->findOrFail(Arr::get($request->getQueryParams(), 'id'), RequestUtil::getActor($request));
$post = $this->posts->findOrFail(Arr::get($request->getQueryParams(), 'id'), RequestUtil::getActor($request));
$include = $this->extractInclude($request);
$this->loadRelations(new Collection([$post]), $include, $request);
return $post;
}
}

View File

@@ -9,9 +9,11 @@
namespace Flarum\Database;
use Flarum\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model as Eloquent;
use Illuminate\Database\Eloquent\Relations\Relation;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use LogicException;
/**
@@ -61,6 +63,14 @@ abstract class AbstractModel extends Eloquent
*/
public static $defaults = [];
/**
* An alias for the table name, used in queries.
*
* @var string|null
* @internal
*/
protected $tableAlias = null;
/**
* {@inheritdoc}
*/
@@ -213,4 +223,41 @@ abstract class AbstractModel extends Eloquent
return parent::__call($method, $arguments);
}
public function newModelQuery()
{
$query = parent::newModelQuery();
if ($this->tableAlias) {
$query->from($this->getTable().' as '.$this->tableAlias);
}
return $query;
}
public function qualifyColumn($column)
{
if (Str::contains($column, '.')) {
return $column;
}
return ($this->tableAlias ?? $this->getTable()).'.'.$column;
}
public function withTableAlias(callable $callback)
{
static $aliasCount = 0;
$this->tableAlias = 'flarum_reserved_'.++$aliasCount;
$result = $callback();
$this->tableAlias = null;
return $result;
}
public function newCollection(array $models = [])
{
return new Collection($models);
}
}

View File

@@ -0,0 +1,50 @@
<?php
/*
* This file is part of Flarum.
*
* For detailed copyright and license information, please view the
* LICENSE file that was distributed with this source code.
*/
namespace Flarum\Database\Eloquent;
use Illuminate\Database\Eloquent\Collection as BaseCollection;
class Collection extends BaseCollection
{
/**
* This is done to prevent conflicts when using visibility scopes.
* Without this, we get the following example query when using a visibility scope
* and eager loading the count of `mentionedBy`:.
*
* ```sql
* SELECT `id`, (
* SELECT count(*)
* FROM `posts` AS `laravel_reserved_0`
* INNER JOIN `post_mentions_post` ON `laravel_reserved_0`.`id` = `post_mentions_post`.`post_id`
* WHERE `posts`.`id` = `post_mentions_post`.`mentions_post_id`
* --- ^^^^^^^ this is the problem, visibility scopes always assume the default table name, rather than
* --- the Laravel auto-generated alias.
*
* AND `TYPE` in ('discussionTagged', 'discussionStickied', 'discussionLocked', 'comment', 'discussionRenamed')
* ) AS `mentioned_by_count`
* FROM `posts`
* WHERE `posts`.`id` in (23642)
* ```
*
* So by applying an alias on the parent query, we prevent Laravel from auto aliasing the sub-query.
*
* @link https://github.com/flarum/framework/pull/3780
*/
public function loadAggregate($relations, $column, $function = null)
{
if ($this->isEmpty()) {
return $this;
}
return $this->first()->withTableAlias(function () use ($relations, $column, $function) {
return parent::loadAggregate($relations, $column, $function);
});
}
}

View File

@@ -313,7 +313,7 @@ class ApiController implements ExtenderInterface
* Allows loading a relationship with additional query modification.
*
* @param string $relation: Relationship name, see load method description.
* @param callable(\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Relations\Relation, \Psr\Http\Message\ServerRequestInterface|null, array): void $callback
* @param array|(callable(\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Relations\Relation, \Psr\Http\Message\ServerRequestInterface|null, array): void) $callback
*
* The callback to modify the query, should accept:
* - \Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Relations\Relation $query: A query object.
@@ -322,7 +322,7 @@ class ApiController implements ExtenderInterface
*
* @return self
*/
public function loadWhere(string $relation, callable $callback): self
public function loadWhere(string $relation, callable $callback): self // @phpstan-ignore-line
{
$this->loadCallables = array_merge($this->loadCallables, [$relation => $callback]);

View File

@@ -18,6 +18,7 @@ use Flarum\Post\Event\Deleted;
use Flarum\User\User;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Query\Expression;
use Staudenmeir\EloquentEagerLimit\HasEagerLimit;
/**
* @property int $id
@@ -42,6 +43,7 @@ class Post extends AbstractModel
{
use EventGeneratorTrait;
use ScopeVisibilityTrait;
use HasEagerLimit;
protected $table = 'posts';