Skip to content

Commit fbf97b3

Browse files
committed
Add message selector unit tests
Remove regexes altogether since they add complexities: 1. Compiling and running regexes on different OTP versions is problematic 2. Holding compiled regexes in rabbit_fifo state is problematic because the snapshot can be sent to another node running a different OTP version An alternative is to compile the regex on demand before each scan.
1 parent 9eb8553 commit fbf97b3

File tree

6 files changed

+1700
-1053
lines changed

6 files changed

+1700
-1053
lines changed

deps/rabbit/src/rabbit_fifo_filter_jms.erl

Lines changed: 78 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -148,12 +148,17 @@ eval0({'not_in', Expr, ValueList}, Headers) ->
148148

149149
eval0({'like', Expr, Pattern, Escape}, Headers) ->
150150
Value = eval0(Expr, Headers),
151-
PatternVal = eval0(Pattern, Headers),
152-
EscapeVal = case Escape of
153-
no_escape -> no_escape;
154-
_ -> eval0(Escape, Headers)
155-
end,
156-
is_like(Value, PatternVal, EscapeVal);
151+
case is_binary(Value) of
152+
true ->
153+
case unicode:characters_to_list(Value) of
154+
L when is_list(L) ->
155+
like_match(L, Pattern, Escape);
156+
_ ->
157+
undefined
158+
end;
159+
false ->
160+
undefined
161+
end;
157162

158163
eval0({'not_like', Expr, Pattern, Escape}, Headers) ->
159164
case eval0({'like', Expr, Pattern, Escape}, Headers) of
@@ -180,14 +185,29 @@ eval0(_, _) ->
180185
%% Helper functions
181186

182187
%% "Comparison or arithmetic with an unknown value always yields an unknown value."
183-
compare(_, undefined, _) -> undefined;
184-
compare(_, _, undefined) -> undefined;
185-
compare('=', Left, Right) -> Left == Right;
186-
compare('<>', Left, Right) -> Left /= Right;
187-
compare('>', Left, Right) -> Left > Right;
188-
compare('<', Left, Right) -> Left < Right;
189-
compare('>=', Left, Right) -> Left >= Right;
190-
compare('<=', Left, Right) -> Left =< Right.
188+
compare(_, undefined, _) ->
189+
undefined;
190+
compare(_, _, undefined) ->
191+
undefined;
192+
%% "Only like type values can be compared.
193+
%% One exception is that it is valid to compare exact numeric values and approximate numeric values.
194+
%% String and Boolean comparison is restricted to = and <>."
195+
compare('=', Left, Right) ->
196+
Left == Right;
197+
compare('<>', Left, Right) ->
198+
Left /= Right;
199+
compare('>', Left, Right) when is_number(Left) andalso is_number(Right) ->
200+
Left > Right;
201+
compare('<', Left, Right) when is_number(Left) andalso is_number(Right) ->
202+
Left < Right;
203+
compare('>=', Left, Right) when is_number(Left) andalso is_number(Right) ->
204+
Left >= Right;
205+
compare('<=', Left, Right) when is_number(Left) andalso is_number(Right) ->
206+
Left =< Right;
207+
compare(_, _, _) ->
208+
%% "If the comparison of non-like type values is attempted,
209+
%% the value of the operation is false."
210+
false.
191211

192212
arithmetic(_, undefined, _) ->
193213
undefined;
@@ -199,15 +219,7 @@ arithmetic('-', Left, Right) when is_number(Left) andalso is_number(Right) ->
199219
Left - Right;
200220
arithmetic('*', Left, Right) when is_number(Left) andalso is_number(Right) ->
201221
Left * Right;
202-
arithmetic('/', Left, Right) when Right =:= 0 andalso Left > 0 ->
203-
infinity;
204-
arithmetic('/', Left, Right) when Right =:= 0 andalso Left < 0 ->
205-
'-infinity';
206-
arithmetic('/', Left, Right) when Right =:= 0 andalso Left =:= 0 ->
207-
'NaN';
208-
arithmetic('/', Left, Right) when is_integer(Left) andalso is_integer(Right) ->
209-
Left div Right;
210-
arithmetic('/', Left, Right) when is_number(Left) andalso is_number(Right) ->
222+
arithmetic('/', Left, Right) when is_number(Left) andalso is_number(Right) andalso Right /= 0 ->
211223
Left / Right;
212224
arithmetic(_, _, _) ->
213225
undefined.
@@ -217,59 +229,53 @@ between(Value, From, To)
217229
From =:= undefined orelse
218230
To =:= undefined ->
219231
undefined;
220-
between(Value, From, To) ->
221-
From =< Value andalso Value =< To.
232+
between(Value, From, To)
233+
when is_number(Value) andalso
234+
is_number(From) andalso
235+
is_number(To) ->
236+
From =< Value andalso Value =< To;
237+
between(_, _, _) ->
238+
%% BETWEEN requires arithmetic expressions
239+
%% "a string cannot be used in an arithmetic expression"
240+
false.
222241

223242
is_in(undefined, _) ->
224243
undefined;
225244
is_in(Value, List) ->
226245
lists:member(Value, List).
227246

228-
is_like(Value, Pattern, Escape)
229-
when is_binary(Value) andalso
230-
is_binary(Pattern) ->
231-
RegexPattern = like_to_regex(Pattern, Escape),
232-
case re:run(Value, RegexPattern, [{capture, none}]) of
233-
match -> true;
234-
nomatch -> false
247+
like_match([], [], _) ->
248+
true;
249+
like_match(_, [], _) ->
250+
false;
251+
like_match([], [Char | _], _) when Char =/= $% ->
252+
false;
253+
like_match([], [$% | Rest], Escape) ->
254+
%% String is empty, pattern starts with % - % can match empty string, continue with rest
255+
like_match([], Rest, Escape);
256+
like_match(S, [Escape, Char | PatRest], Escape) when Escape =/= no_escape ->
257+
%% Found escape character, match the next character literally
258+
case S of
259+
[Char | StrRest] ->
260+
like_match(StrRest, PatRest, Escape);
261+
_ ->
262+
false
235263
end;
236-
is_like(_, _, _) ->
237-
undefined.
238-
239-
%% Convert LIKE pattern to regex
240-
%%
241-
%% TODO compilation should happen when the consumer attaches.
242-
%% Should this happen within rabbit_jms_selector_parser.yrl?
243-
like_to_regex(Pattern, Escape) ->
244-
{ok, Regex} = convert_like_to_regex(binary_to_list(Pattern), Escape),
245-
{ok, MP} = re:compile(<<"^", Regex/binary, "$">>),
246-
MP.
247-
248-
convert_like_to_regex(Pattern, Escape) ->
249-
convert_like_to_regex(Pattern, [], Escape).
250-
251-
convert_like_to_regex([], Acc, _) ->
252-
{ok, iolist_to_binary(lists:reverse(Acc))};
253-
convert_like_to_regex([Esc, Char | Rest], Acc, Esc) when Esc =/= no_escape ->
254-
% Escaped character - take literally
255-
convert_like_to_regex(Rest, [escape_regex_char(Char) | Acc], Esc);
256-
convert_like_to_regex([$% | Rest], Acc, Esc) ->
257-
% % means any sequence of characters (including none)
258-
convert_like_to_regex(Rest, [".*" | Acc], Esc);
259-
convert_like_to_regex([$_ | Rest], Acc, Esc) ->
260-
% _ means any single character
261-
convert_like_to_regex(Rest, [$. | Acc], Esc);
262-
convert_like_to_regex([Char | Rest], Acc, Esc) ->
263-
% Regular character - escape for regex
264-
convert_like_to_regex(Rest, [escape_regex_char(Char) | Acc], Esc).
265-
266-
%% Escape special regex characters
267-
escape_regex_char(Char)
268-
when Char =:= $. orelse Char =:= $* orelse Char =:= $+ orelse
269-
Char =:= $? orelse Char =:= $^ orelse Char =:= $$ orelse
270-
Char =:= $[ orelse Char =:= $] orelse Char =:= $( orelse
271-
Char =:= $) orelse Char =:= ${ orelse Char =:= $} orelse
272-
Char =:= $| orelse Char =:= $\\ ->
273-
[$\\, Char];
274-
escape_regex_char(Char) ->
275-
Char.
264+
like_match([_ | StrRest], [$_ | PatRest], Escape) ->
265+
%% _ matches exactly 1 character
266+
like_match(StrRest, PatRest, Escape);
267+
like_match(S, [$% | PatRest] = Pattern, Escape) ->
268+
%% % matches 0 or more characters - try two paths:
269+
%% 1. Skip % (match 0 characters)
270+
like_match(S, PatRest, Escape) orelse
271+
case S of
272+
[] ->
273+
false;
274+
[_ | StrRest] ->
275+
%% 2. Match current character and keep % in pattern for next iteration
276+
like_match(StrRest, Pattern, Escape)
277+
end;
278+
like_match([Char | StrRest], [Char | PatRest], Escape) ->
279+
like_match(StrRest, PatRest, Escape);
280+
like_match(_, Pattern, _) when is_list(Pattern) ->
281+
false.

0 commit comments

Comments
 (0)