Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Thread scopeBetweenOnly does not omit threads with more members #401

Open
fritz-c opened this issue Jan 8, 2024 · 0 comments
Open

Thread scopeBetweenOnly does not omit threads with more members #401

fritz-c opened this issue Jan 8, 2024 · 0 comments

Comments

@fritz-c
Copy link

fritz-c commented Jan 8, 2024

The scopeBetweenOnly scope for Threads does not exclude threads that include more participants than those given in the argument.

public function scopeBetweenOnly(Builder $query, array $participants)
{
$participantTable = Models::table('participants');
return $query->whereHas('participants', function (Builder $builder) use ($participants, $participantTable) {
return $builder->whereIn('user_id', $participants)
->groupBy($participantTable . '.thread_id')
->select($participantTable . '.thread_id')
->havingRaw('COUNT(' . $participantTable . '.thread_id)=?', [count($participants)]);
});
}

The whereIn('user_id', $participants) part narrows it down so each grouped result will have at most two entries, the cause of the bug.

I extended the class and wrote my own method, which omits some of the table-name/model-name-fetching aspects of the source code, but could be adapted back to the style of the original source. I had also included soft-deleted participants for my own purposes; you can omit that as appropriate:

public function scopeBetweenOnlyIncludingTrashed(Builder $query, array $participants)
{
    return $query
        ->joinSub(
            MessengerParticipant::select('thread_id')
                ->whereIn('user_id', $participants)
                ->withTrashed()
                ->groupBy('thread_id')
                ->havingRaw('COUNT(*)=?', [count($participants)]),
            'party',
            'party.thread_id', '=', 'messenger_threads.id'
        )
        ->whereHas('participants', function (Builder $builder) use ($participants) {
            /** @var Builder<MessengerParticipant> $builder */
            return $builder
                ->whereColumn('thread_id', '=', 'party.thread_id')
                ->withTrashed()
                ->groupBy('thread_id')
                ->select('thread_id')
                ->havingRaw('COUNT(*)=?', [count($participants)]);
        });
}

The SQL query I modeled this after is as follows:

SELECT *
FROM messenger_threads
INNER JOIN (
	SELECT thread_id
  FROM messenger_participants
  WHERE user_id IN (1429, 1507)
  GROUP BY thread_id
  HAVING COUNT(*)=2
) party ON messenger_threads.id = party.thread_id
WHERE EXISTS (
  SELECT thread_id
  FROM messenger_participants
  WHERE party.thread_id = messenger_participants.thread_id
  GROUP BY thread_id
  HAVING COUNT(*)=2
)

Again, that would need adaptation to fit the source code, but I just leave it here as an extra resource to see the approach in use.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant