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 :: map ()}).
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 ]).
@@ -110,7 +113,9 @@ declare(Q0, _Node) when ?amqqueue_is_stream(Q0) ->
110
113
[fun rabbit_queue_type_util :check_auto_delete /1 ,
111
114
fun rabbit_queue_type_util :check_exclusive /1 ,
112
115
fun rabbit_queue_type_util :check_non_durable /1 ,
113
- fun rabbit_stream_queue :check_max_segment_size_bytes /1 ],
116
+ fun check_max_segment_size_bytes /1 ,
117
+ fun check_filter_size /1
118
+ ],
114
119
Q0 ) of
115
120
ok ->
116
121
create_stream (Q0 );
@@ -130,6 +135,18 @@ check_max_segment_size_bytes(Q) ->
130
135
ok
131
136
end .
132
137
138
+ check_filter_size (Q ) ->
139
+ Args = amqqueue :get_arguments (Q ),
140
+ case rabbit_misc :table_lookup (Args , <<" x-stream-filter-size-bytes" >>) of
141
+ undefined ->
142
+ ok ;
143
+ {_Type , Val } when Val > 255 orelse Val < 16 ->
144
+ {protocol_error , precondition_failed ,
145
+ " Invalid value for x-stream-filter-size-bytes" , []};
146
+ _ ->
147
+ ok
148
+ end .
149
+
133
150
create_stream (Q0 ) ->
134
151
Arguments = amqqueue :get_arguments (Q0 ),
135
152
QName = amqqueue :get_name (Q0 ),
@@ -219,7 +236,8 @@ consume(Q, #{no_ack := true}, _)
219
236
consume (Q , #{limiter_active := true }, _State )
220
237
when ? amqqueue_is_stream (Q ) ->
221
238
{error , global_qos_not_supported_for_queue_type };
222
- 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 ) ->
223
241
% % Messages should include the offset as a custom header.
224
242
case check_queue_exists_in_local_node (Q ) of
225
243
ok ->
@@ -244,7 +262,14 @@ consume(Q, Spec, QState0) when ?amqqueue_is_stream(Q) ->
244
262
% % really it should be sent by the stream queue process like classic queues
245
263
% % do
246
264
maybe_send_reply (ChPid , OkMsg ),
247
- 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
248
273
end ;
249
274
Err ->
250
275
Err
@@ -278,6 +303,35 @@ parse_offset_arg({_, V}) ->
278
303
parse_offset_arg (V ) ->
279
304
{error , {invalid_offset_arg , V }}.
280
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
+
281
335
get_local_pid (# stream_client {local_pid = Pid } = State )
282
336
when is_pid (Pid ) ->
283
337
{Pid , State };
@@ -295,14 +349,14 @@ get_local_pid(#stream_client{stream_id = StreamId,
295
349
end .
296
350
297
351
begin_stream (# stream_client {name = QName , readers = Readers0 } = State0 ,
298
- Tag , Offset , Max ) ->
352
+ Tag , Offset , Max , Options ) ->
299
353
{LocalPid , State } = get_local_pid (State0 ),
300
354
case LocalPid of
301
355
undefined ->
302
356
{error , no_local_stream_replica_available };
303
357
_ ->
304
358
CounterSpec = {{? MODULE , QName , Tag , self ()}, []},
305
- {ok , Seg0 } = osiris :init_reader (LocalPid , Offset , CounterSpec ),
359
+ {ok , Seg0 } = osiris :init_reader (LocalPid , Offset , CounterSpec , Options ),
306
360
NextOffset = osiris_log :next_offset (Seg0 ) - 1 ,
307
361
osiris :register_offset_listener (LocalPid , NextOffset ),
308
362
% % TODO: avoid double calls to the same process
@@ -317,7 +371,8 @@ begin_stream(#stream_client{name = QName, readers = Readers0} = State0,
317
371
start_offset = StartOffset ,
318
372
listening_offset = NextOffset ,
319
373
log = Seg0 ,
320
- max = Max },
374
+ max = Max ,
375
+ reader_options = Options },
321
376
{ok , State # stream_client {local_pid = LocalPid ,
322
377
readers = Readers0 #{Tag => Str0 }}}
323
378
end .
@@ -369,7 +424,8 @@ deliver(QSs, #delivery{message = Msg, confirm = Confirm} = Delivery) ->
369
424
lists :foldl (
370
425
fun ({Q , stateless }, {Qs , Actions }) ->
371
426
LeaderPid = amqqueue :get_pid (Q ),
372
- ok = osiris :write (LeaderPid , msg_to_iodata (Msg )),
427
+ ok = osiris :write (LeaderPid ,
428
+ stream_message (Msg , filtering_supported ())),
373
429
{Qs , Actions };
374
430
({Q , S0 }, {Qs , Actions }) ->
375
431
{S , As } = deliver (Confirm , Delivery , S0 ),
@@ -383,8 +439,10 @@ deliver(_Confirm, #delivery{message = Msg, msg_seq_no = MsgId},
383
439
next_seq = Seq ,
384
440
correlation = Correlation0 ,
385
441
soft_limit = SftLmt ,
386
- slow = Slow0 } = State ) ->
387
- 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 )),
388
446
Correlation = case MsgId of
389
447
undefined ->
390
448
Correlation0 ;
@@ -401,6 +459,25 @@ deliver(_Confirm, #delivery{message = Msg, msg_seq_no = MsgId},
401
459
correlation = Correlation ,
402
460
slow = Slow }, Actions }.
403
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
+
404
481
-spec dequeue (_ , _ , _ , _ , client ()) -> no_return ().
405
482
dequeue (_ , _ , _ , _ , # stream_client {name = Name }) ->
406
483
{protocol_error , not_implemented , " basic.get not supported by stream queues ~ts " ,
@@ -448,12 +525,14 @@ handle_event(_QName, {stream_local_member_change, Pid}, #stream_client{local_pid
448
525
handle_event (_QName , {stream_local_member_change , Pid }, State = # stream_client {name = QName ,
449
526
readers = Readers0 }) ->
450
527
rabbit_log :debug (" Local member change event for ~tp " , [QName ]),
451
- Readers1 = maps :fold (fun (T , # stream {log = Log0 } = S0 , Acc ) ->
528
+ Readers1 = maps :fold (fun (T , # stream {log = Log0 , reader_options = Options } = S0 , Acc ) ->
452
529
Offset = osiris_log :next_offset (Log0 ),
453
530
osiris_log :close (Log0 ),
454
531
CounterSpec = {{? MODULE , QName , self ()}, []},
455
- rabbit_log :debug (" Re-creating Osiris reader for consumer ~tp at offset ~tp " , [T , Offset ]),
456
- {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 ),
457
536
NextOffset = osiris_log :next_offset (Log1 ) - 1 ,
458
537
rabbit_log :debug (" Registering offset listener at offset ~tp " , [NextOffset ]),
459
538
osiris :register_offset_listener (Pid , NextOffset ),
@@ -761,7 +840,8 @@ init(Q) when ?is_amqqueue(Q) ->
761
840
name = amqqueue :get_name (Q ),
762
841
leader = Leader ,
763
842
writer_id = WriterId ,
764
- soft_limit = SoftLimit }};
843
+ soft_limit = SoftLimit ,
844
+ filtering_supported = filtering_supported ()}};
765
845
{ok , stream_not_found , _ } ->
766
846
{error , stream_not_found };
767
847
{error , coordinator_unavailable } = E ->
@@ -865,34 +945,29 @@ delete_replica(VHost, Name, Node) ->
865
945
make_stream_conf (Q ) ->
866
946
QName = amqqueue :get_name (Q ),
867
947
Name = stream_name (QName ),
868
- % % MaxLength = args_policy_lookup(<<"max-length">>, policy_precedence/2, Q),
869
- MaxBytes = args_policy_lookup (<<" max-length-bytes" >>, fun policy_precedence /2 , Q ),
870
- MaxAge = max_age (args_policy_lookup (<<" max-age" >>, fun policy_precedence /2 , Q )),
871
- MaxSegmentSizeBytes = args_policy_lookup (<<" stream-max-segment-size-bytes" >>, fun policy_precedence /2 , Q ),
872
948
Formatter = {? MODULE , format_osiris_event , [QName ]},
873
- Retention = lists :filter (fun ({_ , R }) ->
874
- R =/= undefined
875
- end , [{max_bytes , MaxBytes },
876
- {max_age , MaxAge }]),
877
- add_if_defined (max_segment_size_bytes , MaxSegmentSizeBytes ,
878
- #{reference => QName ,
879
- name => Name ,
880
- retention => Retention ,
881
- event_formatter => Formatter ,
882
- epoch => 1 }).
949
+ update_stream_conf (Q , #{reference => QName ,
950
+ name => Name ,
951
+ event_formatter => Formatter ,
952
+ epoch => 1 }).
883
953
884
954
update_stream_conf (undefined , #{} = Conf ) ->
885
955
Conf ;
886
956
update_stream_conf (Q , #{} = Conf ) when ? is_amqqueue (Q ) ->
887
957
MaxBytes = args_policy_lookup (<<" max-length-bytes" >>, fun policy_precedence /2 , Q ),
888
958
MaxAge = max_age (args_policy_lookup (<<" max-age" >>, fun policy_precedence /2 , Q )),
889
- MaxSegmentSizeBytes = args_policy_lookup (<<" stream-max-segment-size-bytes" >>, fun policy_precedence /2 , Q ),
959
+ MaxSegmentSizeBytes = args_policy_lookup (<<" stream-max-segment-size-bytes" >>,
960
+ fun policy_precedence /2 , Q ),
961
+ FilterSizeBytes = args_policy_lookup (<<" stream-filter-size-bytes" >>,
962
+ fun policy_precedence /2 , Q ),
890
963
Retention = lists :filter (fun ({_ , R }) ->
891
964
R =/= undefined
892
965
end , [{max_bytes , MaxBytes },
893
966
{max_age , MaxAge }]),
894
- add_if_defined (max_segment_size_bytes , MaxSegmentSizeBytes ,
895
- Conf #{retention => Retention }).
967
+ add_if_defined (
968
+ filter_size , FilterSizeBytes ,
969
+ add_if_defined (max_segment_size_bytes , MaxSegmentSizeBytes ,
970
+ Conf #{retention => Retention })).
896
971
897
972
add_if_defined (_ , undefined , Map ) ->
898
973
Map ;
@@ -1047,7 +1122,8 @@ notify_decorators(Q) when ?is_amqqueue(Q) ->
1047
1122
1048
1123
resend_all (# stream_client {leader = LeaderPid ,
1049
1124
writer_id = WriterId ,
1050
- correlation = Corrs } = State ) ->
1125
+ correlation = Corrs ,
1126
+ filtering_supported = FilteringSupported } = State ) ->
1051
1127
Msgs = lists :sort (maps :values (Corrs )),
1052
1128
case Msgs of
1053
1129
[] -> ok ;
@@ -1056,7 +1132,8 @@ resend_all(#stream_client{leader = LeaderPid,
1056
1132
[Seq , maps :size (Corrs )])
1057
1133
end ,
1058
1134
[begin
1059
- ok = osiris :write (LeaderPid , WriterId , Seq , msg_to_iodata (Msg ))
1135
+ ok = osiris :write (LeaderPid , WriterId , Seq ,
1136
+ stream_message (Msg , FilteringSupported ))
1060
1137
end || {Seq , Msg } <- Msgs ],
1061
1138
State .
1062
1139
@@ -1089,3 +1166,6 @@ list_with_minimum_quorum() ->
1089
1166
end , rabbit_amqqueue :list_local_stream_queues ()).
1090
1167
1091
1168
is_stateful () -> true .
1169
+
1170
+ filtering_supported () ->
1171
+ rabbit_feature_flags :is_enabled (stream_filtering ).
0 commit comments