47
47
-export ([update_stream_conf /2 ]).
48
48
-export ([readers /1 ]).
49
49
50
- -export ([parse_offset_arg /1 ]).
50
+ -export ([parse_offset_arg /1 ,
51
+ filter_spec /1 ]).
51
52
52
53
-export ([status /2 ,
53
54
tracking_status /2 ,
72
73
max :: non_neg_integer (),
73
74
start_offset = 0 :: non_neg_integer (),
74
75
listening_offset = 0 :: non_neg_integer (),
75
- log :: undefined | osiris_log :state ()}).
76
+ log :: undefined | osiris_log :state (),
77
+ reader_options :: osiris :reader_options ()}).
76
78
77
79
-record (stream_client , {stream_id :: string (),
78
80
name :: term (),
83
85
soft_limit :: non_neg_integer (),
84
86
slow = false :: boolean (),
85
87
readers = #{} :: #{term () => # stream {}},
86
- writer_id :: binary ()
88
+ writer_id :: binary (),
89
+ filtering_supported :: boolean ()
87
90
}).
88
91
89
92
-import (rabbit_queue_type_util , [args_policy_lookup /3 ]).
@@ -233,7 +236,8 @@ consume(Q, #{no_ack := true}, _)
233
236
consume (Q , #{limiter_active := true }, _State )
234
237
when ? amqqueue_is_stream (Q ) ->
235
238
{error , global_qos_not_supported_for_queue_type };
236
- consume (Q , Spec , QState0 ) when ? amqqueue_is_stream (Q ) ->
239
+ consume (Q , Spec ,
240
+ # stream_client {filtering_supported = FilteringSupported } = QState0 ) when ? amqqueue_is_stream (Q ) ->
237
241
% % Messages should include the offset as a custom header.
238
242
case check_queue_exists_in_local_node (Q ) of
239
243
ok ->
@@ -258,7 +262,14 @@ consume(Q, Spec, QState0) when ?amqqueue_is_stream(Q) ->
258
262
% % really it should be sent by the stream queue process like classic queues
259
263
% % do
260
264
maybe_send_reply (ChPid , OkMsg ),
261
- begin_stream (QState0 , ConsumerTag , OffsetSpec , ConsumerPrefetchCount )
265
+ FilterSpec = filter_spec (Args ),
266
+ case {FilterSpec , FilteringSupported } of
267
+ {#{filter_spec := _ }, false } ->
268
+ {protocol_error , precondition_failed , " Filtering is not supported" , []};
269
+ _ ->
270
+ begin_stream (QState0 , ConsumerTag , OffsetSpec ,
271
+ ConsumerPrefetchCount , FilterSpec )
272
+ end
262
273
end ;
263
274
Err ->
264
275
Err
@@ -292,6 +303,35 @@ parse_offset_arg({_, V}) ->
292
303
parse_offset_arg (V ) ->
293
304
{error , {invalid_offset_arg , V }}.
294
305
306
+ filter_spec (Args ) ->
307
+ Filters = case lists :keysearch (<<" x-stream-filter" >>, 1 , Args ) of
308
+ {value , {_ , array , FilterValues }} ->
309
+ lists :foldl (fun ({longstr , V }, Acc ) ->
310
+ [V ] ++ Acc ;
311
+ (_ , Acc ) ->
312
+ Acc
313
+ end , [], FilterValues );
314
+ {value , {_ , longstr , FilterValue }} ->
315
+ [FilterValue ];
316
+ _ ->
317
+ undefined
318
+ end ,
319
+ MatchUnfiltered = case lists :keysearch (<<" x-stream-match-unfiltered" >>, 1 , Args ) of
320
+ {value , {_ , bool , Match }} when is_list (Filters ) ->
321
+ Match ;
322
+ _ when is_list (Filters ) ->
323
+ false ;
324
+ _ ->
325
+ undefined
326
+ end ,
327
+ case MatchUnfiltered of
328
+ undefined ->
329
+ #{};
330
+ MU ->
331
+ #{filter_spec =>
332
+ #{filters => Filters , match_unfiltered => MU }}
333
+ end .
334
+
295
335
get_local_pid (# stream_client {local_pid = Pid } = State )
296
336
when is_pid (Pid ) ->
297
337
{Pid , State };
@@ -309,14 +349,14 @@ get_local_pid(#stream_client{stream_id = StreamId,
309
349
end .
310
350
311
351
begin_stream (# stream_client {name = QName , readers = Readers0 } = State0 ,
312
- Tag , Offset , Max ) ->
352
+ Tag , Offset , Max , Options ) ->
313
353
{LocalPid , State } = get_local_pid (State0 ),
314
354
case LocalPid of
315
355
undefined ->
316
356
{error , no_local_stream_replica_available };
317
357
_ ->
318
358
CounterSpec = {{? MODULE , QName , Tag , self ()}, []},
319
- {ok , Seg0 } = osiris :init_reader (LocalPid , Offset , CounterSpec ),
359
+ {ok , Seg0 } = osiris :init_reader (LocalPid , Offset , CounterSpec , Options ),
320
360
NextOffset = osiris_log :next_offset (Seg0 ) - 1 ,
321
361
osiris :register_offset_listener (LocalPid , NextOffset ),
322
362
% % TODO: avoid double calls to the same process
@@ -331,7 +371,8 @@ begin_stream(#stream_client{name = QName, readers = Readers0} = State0,
331
371
start_offset = StartOffset ,
332
372
listening_offset = NextOffset ,
333
373
log = Seg0 ,
334
- max = Max },
374
+ max = Max ,
375
+ reader_options = Options },
335
376
{ok , State # stream_client {local_pid = LocalPid ,
336
377
readers = Readers0 #{Tag => Str0 }}}
337
378
end .
@@ -383,7 +424,8 @@ deliver(QSs, #delivery{message = Msg, confirm = Confirm} = Delivery) ->
383
424
lists :foldl (
384
425
fun ({Q , stateless }, {Qs , Actions }) ->
385
426
LeaderPid = amqqueue :get_pid (Q ),
386
- ok = osiris :write (LeaderPid , msg_to_iodata (Msg )),
427
+ ok = osiris :write (LeaderPid ,
428
+ stream_message (Msg , filtering_supported ())),
387
429
{Qs , Actions };
388
430
({Q , S0 }, {Qs , Actions }) ->
389
431
{S , As } = deliver (Confirm , Delivery , S0 ),
@@ -397,8 +439,10 @@ deliver(_Confirm, #delivery{message = Msg, msg_seq_no = MsgId},
397
439
next_seq = Seq ,
398
440
correlation = Correlation0 ,
399
441
soft_limit = SftLmt ,
400
- slow = Slow0 } = State ) ->
401
- ok = osiris :write (LeaderPid , WriterId , Seq , msg_to_iodata (Msg )),
442
+ slow = Slow0 ,
443
+ filtering_supported = FilteringSupported } = State ) ->
444
+ ok = osiris :write (LeaderPid , WriterId , Seq ,
445
+ stream_message (Msg , FilteringSupported )),
402
446
Correlation = case MsgId of
403
447
undefined ->
404
448
Correlation0 ;
@@ -415,6 +459,25 @@ deliver(_Confirm, #delivery{message = Msg, msg_seq_no = MsgId},
415
459
correlation = Correlation ,
416
460
slow = Slow }, Actions }.
417
461
462
+ stream_message (Msg , _FilteringSupported = true ) ->
463
+ MsgData = msg_to_iodata (Msg ),
464
+ case filter_header (Msg ) of
465
+ {_ , longstr , Value } ->
466
+ {Value , MsgData };
467
+ _ ->
468
+ MsgData
469
+ end ;
470
+ stream_message (Msg , _FilteringSupported = false ) ->
471
+ msg_to_iodata (Msg ).
472
+
473
+ filter_header (Msg ) ->
474
+ basic_header (<<" x-stream-filter-value" >>, Msg ).
475
+
476
+ basic_header (Key , # basic_message {content = Content }) ->
477
+ Headers = rabbit_basic :extract_headers (Content ),
478
+ rabbit_basic :header (Key , Headers ).
479
+
480
+
418
481
-spec dequeue (_ , _ , _ , _ , client ()) -> no_return ().
419
482
dequeue (_ , _ , _ , _ , # stream_client {name = Name }) ->
420
483
{protocol_error , not_implemented , " basic.get not supported by stream queues ~ts " ,
@@ -462,12 +525,14 @@ handle_event(_QName, {stream_local_member_change, Pid}, #stream_client{local_pid
462
525
handle_event (_QName , {stream_local_member_change , Pid }, State = # stream_client {name = QName ,
463
526
readers = Readers0 }) ->
464
527
rabbit_log :debug (" Local member change event for ~tp " , [QName ]),
465
- Readers1 = maps :fold (fun (T , # stream {log = Log0 } = S0 , Acc ) ->
528
+ Readers1 = maps :fold (fun (T , # stream {log = Log0 , reader_options = Options } = S0 , Acc ) ->
466
529
Offset = osiris_log :next_offset (Log0 ),
467
530
osiris_log :close (Log0 ),
468
531
CounterSpec = {{? MODULE , QName , self ()}, []},
469
- rabbit_log :debug (" Re-creating Osiris reader for consumer ~tp at offset ~tp " , [T , Offset ]),
470
- {ok , Log1 } = osiris :init_reader (Pid , Offset , CounterSpec ),
532
+ rabbit_log :debug (" Re-creating Osiris reader for consumer ~tp at offset ~tp "
533
+ " with options ~tp " ,
534
+ [T , Offset , Options ]),
535
+ {ok , Log1 } = osiris :init_reader (Pid , Offset , CounterSpec , Options ),
471
536
NextOffset = osiris_log :next_offset (Log1 ) - 1 ,
472
537
rabbit_log :debug (" Registering offset listener at offset ~tp " , [NextOffset ]),
473
538
osiris :register_offset_listener (Pid , NextOffset ),
@@ -775,7 +840,8 @@ init(Q) when ?is_amqqueue(Q) ->
775
840
name = amqqueue :get_name (Q ),
776
841
leader = Leader ,
777
842
writer_id = WriterId ,
778
- soft_limit = SoftLimit }};
843
+ soft_limit = SoftLimit ,
844
+ filtering_supported = filtering_supported ()}};
779
845
{ok , stream_not_found , _ } ->
780
846
{error , stream_not_found };
781
847
{error , coordinator_unavailable } = E ->
@@ -1056,7 +1122,8 @@ notify_decorators(Q) when ?is_amqqueue(Q) ->
1056
1122
1057
1123
resend_all (# stream_client {leader = LeaderPid ,
1058
1124
writer_id = WriterId ,
1059
- correlation = Corrs } = State ) ->
1125
+ correlation = Corrs ,
1126
+ filtering_supported = FilteringSupported } = State ) ->
1060
1127
Msgs = lists :sort (maps :values (Corrs )),
1061
1128
case Msgs of
1062
1129
[] -> ok ;
@@ -1065,7 +1132,8 @@ resend_all(#stream_client{leader = LeaderPid,
1065
1132
[Seq , maps :size (Corrs )])
1066
1133
end ,
1067
1134
[begin
1068
- ok = osiris :write (LeaderPid , WriterId , Seq , msg_to_iodata (Msg ))
1135
+ ok = osiris :write (LeaderPid , WriterId , Seq ,
1136
+ stream_message (Msg , FilteringSupported ))
1069
1137
end || {Seq , Msg } <- Msgs ],
1070
1138
State .
1071
1139
@@ -1098,3 +1166,6 @@ list_with_minimum_quorum() ->
1098
1166
end , rabbit_amqqueue :list_local_stream_queues ()).
1099
1167
1100
1168
is_stateful () -> true .
1169
+
1170
+ filtering_supported () ->
1171
+ rabbit_feature_flags :is_enabled (stream_filtering ).
0 commit comments