2626#include " column/chunk.h"
2727#include " column/column_helper.h"
2828#include " exec/file_scanner/json_scanner.h"
29+ #include " exec/file_scanner/stream_source_meta.h"
2930#include " exec/json_parser.h"
3031#include " exprs/cast_expr.h"
3132#include " exprs/column_ref.h"
@@ -160,6 +161,8 @@ Status AvroScanner::open() {
160161 }
161162 ++_counter->num_files_read ;
162163
164+ _meta_col_by_slot_id = build_stream_source_meta_columns (_scan_range.params .stream_source_meta_columns );
165+ int column_index = 0 ;
163166 for (size_t i = 0 ; i < _src_slot_descriptors.size (); ++i) {
164167 const auto & desc = _src_slot_descriptors[i];
165168 if (desc == nullptr ) {
@@ -169,6 +172,13 @@ Status AvroScanner::open() {
169172 // Remember the intermediate avro type per slot so the no-jsonpath path can dispatch
170173 // _construct_column on the source column's type rather than the destination type.
171174 _slot_id_to_avro_type.emplace (desc->id (), _avro_types[i]);
175+ // A hidden metadata slot is filled from the message meta, not the payload. The by-name null-fill
176+ // path iterates chunk columns, so key its descriptor by chunk column index (the append order in
177+ // _create_src_chunk, which skips null slots just like this loop).
178+ if (auto m = _meta_col_by_slot_id.find (desc->id ()); m != _meta_col_by_slot_id.end ()) {
179+ _meta_col_by_index.emplace (column_index, m->second );
180+ }
181+ column_index++;
172182 }
173183 _init_data_idx_to_slot_once = false ;
174184 return Status::OK ();
@@ -205,20 +215,33 @@ void AvroScanner::_report_error(const std::string& line, const std::string& err_
205215 _state->append_error_msg_to_file (line, err_msg);
206216}
207217
208- Status AvroScanner::_construct_row (const avro_value_t & avro_value, Chunk* chunk) {
218+ Status AvroScanner::_construct_row (const avro_value_t & avro_value, Chunk* chunk, const StreamMessageMeta* meta ) {
209219 size_t slot_size = _src_slot_descriptors.size ();
210220 size_t jsonpath_size = _json_paths.size ();
221+ // Hidden metadata columns carry no jsonpath, so they are transparent to the positional
222+ // slot->jsonpath mapping: a jsonpath maps to the i-th non-metadata slot. This keeps payload columns
223+ // aligned even when a metadata column sits before jsonpath-backed columns.
224+ size_t meta_count = 0 ;
211225 for (size_t i = 0 ; i < slot_size; i++) {
212226 if (_src_slot_descriptors[i] == nullptr ) {
213227 continue ;
214228 }
215229 auto column = down_cast<NullableColumn*>(chunk->get_column_raw_ptr_by_slot_id (_src_slot_descriptors[i]->id ()));
216- if (UNLIKELY (i >= jsonpath_size)) {
230+ // Hidden source-metadata slots are filled from the message meta (by slot id), not the payload,
231+ // before the jsonpath extraction below.
232+ if (auto it = _meta_col_by_slot_id.find (_src_slot_descriptors[i]->id ());
233+ UNLIKELY (it != _meta_col_by_slot_id.end ())) {
234+ RETURN_IF_ERROR (fill_stream_source_meta_column (it->second .kind , meta, column));
235+ meta_count++;
236+ continue ;
237+ }
238+ size_t jp = i - meta_count;
239+ if (UNLIKELY (jp >= jsonpath_size)) {
217240 column->append_nulls (1 );
218241 continue ;
219242 }
220243 avro_value_t output_value;
221- auto st = _extract_field (avro_value, _json_paths[i ], &output_value);
244+ auto st = _extract_field (avro_value, _json_paths[jp ], &output_value);
222245 if (LIKELY (st.ok ())) {
223246 // Dispatch on the intermediate avro type (which matches the source column created in
224247 // _create_src_chunk), not the destination type. Otherwise a complex column whose
@@ -241,6 +264,9 @@ Status AvroScanner::_parse_avro(Chunk* chunk, const std::shared_ptr<SequentialFi
241264 DCHECK_EQ (0 , chunk->num_rows ());
242265 for (size_t num_rows = chunk->num_rows (); num_rows < capacity; /* */ ) {
243266 avro_value_t avro_value;
267+ // Routine-load source-metadata for this message (null for non-routine-load); read from the pipe
268+ // buffer below, or injected by the test in BE_TEST.
269+ const StreamMessageMeta* meta = nullptr ;
244270#ifdef BE_TEST
245271 // In general, we want to test component injection schemastr.
246272 avro_schema_error_t error;
@@ -273,6 +299,7 @@ Status AvroScanner::_parse_avro(Chunk* chunk, const std::shared_ptr<SequentialFi
273299 return Status::InternalError (err_msg);
274300 }
275301 free (avro_as_json);
302+ meta = _test_meta;
276303#else
277304 const uint8_t * data{};
278305 size_t length = 0 ;
@@ -284,6 +311,7 @@ Status AvroScanner::_parse_avro(Chunk* chunk, const std::shared_ptr<SequentialFi
284311 }
285312 data = reinterpret_cast <uint8_t *>(_parser_buf->ptr );
286313 length = _parser_buf->remaining ();
314+ meta = stream_source_meta_of (_parser_buf);
287315 serdes_schema_t * schema;
288316 serdes_err_t err =
289317 serdes_deserialize_avro (_serdes, &avro_value, &schema, data, length, _err_buf, sizeof (_err_buf));
@@ -299,7 +327,7 @@ Status AvroScanner::_parse_avro(Chunk* chunk, const std::shared_ptr<SequentialFi
299327 size_t chunk_row_num = chunk->num_rows ();
300328 Status st = Status::OK ();
301329 if (!_json_paths.empty ()) {
302- st = _construct_row (avro_value, chunk);
330+ st = _construct_row (avro_value, chunk, meta );
303331 } else {
304332 if (!_init_data_idx_to_slot_once) {
305333 size_t element_count;
@@ -320,7 +348,7 @@ Status AvroScanner::_parse_avro(Chunk* chunk, const std::shared_ptr<SequentialFi
320348
321349 _init_data_idx_to_slot_once = true ;
322350 }
323- st = _construct_row_without_jsonpath (avro_value, chunk);
351+ st = _construct_row_without_jsonpath (avro_value, chunk, meta );
324352 }
325353 if (!st.ok ()) {
326354 if (_counter->num_rows_filtered ++ < MAX_ERROR_LINES_IN_FILE ) {
@@ -338,7 +366,8 @@ Status AvroScanner::_parse_avro(Chunk* chunk, const std::shared_ptr<SequentialFi
338366 return Status::OK ();
339367}
340368
341- Status AvroScanner::_construct_row_without_jsonpath (const avro_value_t & avro_value, Chunk* chunk) {
369+ Status AvroScanner::_construct_row_without_jsonpath (const avro_value_t & avro_value, Chunk* chunk,
370+ const StreamMessageMeta* meta) {
342371 _found_columns.assign (chunk->num_columns (), false );
343372 size_t element_count = _data_idx_to_fieldname.size ();
344373 avro_value_t element_value;
@@ -362,6 +391,12 @@ Status AvroScanner::_construct_row_without_jsonpath(const avro_value_t& avro_val
362391 continue ;
363392 }
364393 auto slot_desc = itr->second ;
394+ // A metadata alias is filled from the message meta in the null-fill pass, never from the
395+ // payload; if a payload field uses the same name, skip it (cache as id = -1) so metadata wins.
396+ if (_meta_col_by_slot_id.find (slot_desc->id ()) != _meta_col_by_slot_id.end ()) {
397+ slot_info.id = -1 ;
398+ continue ;
399+ }
365400 slot_info.id = slot_desc->id ();
366401 // Store the intermediate avro type (not the destination type) so _construct_column
367402 // dispatches on the type the source column was built with. See _construct_row.
@@ -386,7 +421,13 @@ Status AvroScanner::_construct_row_without_jsonpath(const avro_value_t& avro_val
386421 for (int i = 0 ; i < _found_columns.size (); i++) {
387422 if (UNLIKELY (!_found_columns[i])) {
388423 auto * column = chunk->get_column_raw_ptr_by_index (i);
389- column->append_nulls (1 );
424+ // A hidden metadata slot always lands here (the by-name resolve above skips any same-named
425+ // payload field); fill it from the message meta, NULL when the buffer carries none.
426+ if (auto it = _meta_col_by_index.find (i); it != _meta_col_by_index.end ()) {
427+ RETURN_IF_ERROR (fill_stream_source_meta_column (it->second .kind , meta, column));
428+ } else {
429+ column->append_nulls (1 );
430+ }
390431 }
391432 }
392433 return Status::OK ();
0 commit comments