Current section

Files

Jump to
nostrum src nostrum_message_cache_qlc.erl
Raw

src/nostrum_message_cache_qlc.erl

% Native QLC operations.
%
% Using QLC from Elixir we pay the price of having to recompile our query
% handle every time we run it. For longer scans like `reduce` this is less of a
% problem, but for queries that users expect to be fast, like a `get` from an
% ETS table, more than a millisecond is unacceptable.
%
% Apart from the recompilation price, queries written using QLC's
% `string_to_handle` also have worse performance than queries written in native
% Erlang, see
% https://elixirforum.com/t/performance-discrepancies-with-using-qlc-queries-written-in-erlang-and-elixir/56006.
% I assume this is caused by the Erlang parse transform doing smart things at compile time.
%
% NOTE: None of the functions in this module make any guarantees about the
% sorting of the output. If you need a specific order, you must sort the output
% yourself either by using `qlc:keysort` or by using `lists:sort` on the output.
-module(nostrum_message_cache_qlc).
-export([
by_channel/4,
by_channel_and_author/5,
by_author/4,
all_message_ids_in_channel/2
]).
-include_lib("stdlib/include/qlc.hrl").
% The matching on the cache names here is smelly. But we need it for the
% built-in caches for now.
% These must be selected carefully so that QLC can plan using the indices properly.
-define(MNESIA_FORMAT, {_Tag, MessageId, ChannelId, AuthorId, Message}).
% Fetch all messages in a channel before a given message id,
% and after another given message id. Accepts infinity as a before
% value since erlang term ordering makes atoms always larger than integers.
-spec by_channel(
'Elixir.Nostrum.Struct.Channel':id(), non_neg_integer() | infinity, non_neg_integer(), qlc:query_handle()
) -> qlc:query_handle().
by_channel(RequestedChannelId, After, Before, Handle) ->
qlc:q([
Message
|| {_Tag, MessageId, ChannelId, _, Message} <- Handle,
ChannelId =:= RequestedChannelId,
MessageId =< Before,
MessageId >= After
]).
% lookup the IDs of all cached messages for a given channel.
-spec all_message_ids_in_channel('Elixir.Nostrum.Struct.Channel':id(), qlc:query_handle()) ->
qlc:query_handle().
all_message_ids_in_channel(RequestedChannelId, Handle) ->
qlc:q([
MessageId
|| {_Tag, MessageId, ChannelId, _, _Message} <- Handle,
ChannelId =:= RequestedChannelId
]).
% Lookup all cached messages in a channel by a specific user.
% The output is not sorted.
-spec by_channel_and_author(
'Elixir.Nostrum.Struct.Channel':id(), 'Elixir.Nostrum.Struct.Message':id(), non_neg_integer(), non_neg_integer() | infinity, qlc:query_handle()
) -> qlc:query_handle().
by_channel_and_author(RequestedChannelId, RequestedUserId, After, Before, Handle) ->
qlc:q([
Message
|| {_Tag, MessageId, ChannelId, AuthorId, Message} <- Handle,
ChannelId =:= RequestedChannelId,
AuthorId =:= RequestedUserId,
MessageId =< Before,
MessageId >= After
]).
% Lookup all cached messages by a specific user.
% with a message id greater than After and less than Before.
-spec by_author(
'Elixir.Nostrum.Struct.User':id(),
After :: non_neg_integer(),
Before :: non_neg_integer(),
qlc:query_handle()
) -> qlc:query_handle().
by_author(RequestedUserId, After, Before, Handle) ->
qlc:q([
Message
|| {_Tag, MessageId, _ChannelId, AuthorId, Message} <- Handle,
AuthorId =:= RequestedUserId,
MessageId =< Before,
MessageId >= After
]).