Coverage Report

Created: 2026-10-26 08:40

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
be/src/format/csv/csv_reader.cpp
Line
Count
Source
1
// Licensed to the Apache Software Foundation (ASF) under one
2
// or more contributor license agreements.  See the NOTICE file
3
// distributed with this work for additional information
4
// regarding copyright ownership.  The ASF licenses this file
5
// to you under the Apache License, Version 2.0 (the
6
// "License"); you may not use this file except in compliance
7
// with the License.  You may obtain a copy of the License at
8
//
9
//   http://www.apache.org/licenses/LICENSE-2.0
10
//
11
// Unless required by applicable law or agreed to in writing,
12
// software distributed under the License is distributed on an
13
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
// KIND, either express or implied.  See the License for the
15
// specific language governing permissions and limitations
16
// under the License.
17
18
#include "format/csv/csv_reader.h"
19
20
#include <fmt/format.h>
21
#include <gen_cpp/PlanNodes_types.h>
22
#include <gen_cpp/Types_types.h>
23
#include <glog/logging.h>
24
25
#include <algorithm>
26
#include <cstddef>
27
#include <map>
28
#include <memory>
29
#include <numeric>
30
#include <ostream>
31
#include <regex>
32
#include <utility>
33
34
#include "common/compiler_util.h" // IWYU pragma: keep
35
#include "common/config.h"
36
#include "common/consts.h"
37
#include "common/status.h"
38
#include "core/block/block.h"
39
#include "core/block/column_with_type_and_name.h"
40
#include "core/column/column_nullable.h"
41
#include "core/column/column_string.h"
42
#include "core/data_type/data_type_factory.hpp"
43
#include "core/data_type_serde/data_type_string_serde.h"
44
#include "exec/scan/scanner.h"
45
#include "format/file_reader/new_plain_binary_line_reader.h"
46
#include "format/file_reader/new_plain_text_line_reader.h"
47
#include "format/line_reader.h"
48
#include "io/file_factory.h"
49
#include "io/fs/broker_file_reader.h"
50
#include "io/fs/buffered_reader.h"
51
#include "io/fs/file_reader.h"
52
#include "io/fs/obj_storage_file_reader.h"
53
#include "io/fs/tracing_file_reader.h"
54
#include "runtime/descriptors.h"
55
#include "runtime/runtime_state.h"
56
#include "util/decompressor.h"
57
#include "util/string_util.h"
58
#include "util/utf8_check.h"
59
60
namespace doris {
61
class RuntimeProfile;
62
class IColumn;
63
namespace io {
64
struct IOContext;
65
enum class FileCachePolicy : uint8_t;
66
} // namespace io
67
} // namespace doris
68
69
namespace doris {
70
71
namespace {
72
73
778
size_t columns_byte_size(const std::vector<MutableColumnPtr>& columns) {
74
778
    size_t bytes = 0;
75
840
    for (const auto& column : columns) {
76
840
        DCHECK(column.get() != nullptr);
77
840
        bytes += column->byte_size();
78
840
    }
79
778
    return bytes;
80
778
}
81
82
} // namespace
83
84
4
void EncloseCsvTextFieldSplitter::do_split(const Slice& line, std::vector<Slice>* splitted_values) {
85
4
    const char* data = line.data;
86
4
    const auto& column_sep_positions = _text_line_reader_ctx->column_sep_positions();
87
4
    size_t value_start_offset = 0;
88
20
    for (auto idx : column_sep_positions) {
89
20
        process_value_func(data, value_start_offset, idx - value_start_offset, _trimming_char,
90
20
                           splitted_values);
91
20
        value_start_offset = idx + _value_sep_len;
92
20
    }
93
4
    if (line.size >= value_start_offset) {
94
        // process the last column
95
4
        process_value_func(data, value_start_offset, line.size - value_start_offset, _trimming_char,
96
4
                           splitted_values);
97
4
    }
98
4
}
99
100
void PlainCsvTextFieldSplitter::_split_field_single_char(const Slice& line,
101
850
                                                         std::vector<Slice>* splitted_values) {
102
850
    const char* data = line.data;
103
850
    const size_t size = line.size;
104
850
    size_t value_start = 0;
105
1.07M
    for (size_t i = 0; i < size; ++i) {
106
1.07M
        if (data[i] == _value_sep[0]) {
107
2.45k
            process_value_func(data, value_start, i - value_start, _trimming_char, splitted_values);
108
2.45k
            value_start = i + _value_sep_len;
109
2.45k
        }
110
1.07M
    }
111
850
    process_value_func(data, value_start, size - value_start, _trimming_char, splitted_values);
112
850
}
113
114
void PlainCsvTextFieldSplitter::_split_field_multi_char(const Slice& line,
115
9
                                                        std::vector<Slice>* splitted_values) {
116
9
    size_t start = 0;  // point to the start pos of next col value.
117
9
    size_t curpos = 0; // point to the start pos of separator matching sequence.
118
119
    // value_sep : AAAA
120
    // line.data : 1234AAAA5678
121
    // -> 1234,5678
122
123
    //    start   start
124
    //      ▼       ▼
125
    //      1234AAAA5678\0
126
    //          ▲       ▲
127
    //      curpos     curpos
128
129
    //kmp
130
9
    std::vector<int> next(_value_sep_len);
131
9
    next[0] = -1;
132
18
    for (int i = 1, j = -1; i < _value_sep_len; i++) {
133
9
        while (j > -1 && _value_sep[i] != _value_sep[j + 1]) {
134
0
            j = next[j];
135
0
        }
136
9
        if (_value_sep[i] == _value_sep[j + 1]) {
137
6
            j++;
138
6
        }
139
9
        next[i] = j;
140
9
    }
141
142
486
    for (int i = 0, j = -1; i < line.size; i++) {
143
        // i : line
144
        // j : _value_sep
145
502
        while (j > -1 && line[i] != _value_sep[j + 1]) {
146
25
            j = next[j];
147
25
        }
148
477
        if (line[i] == _value_sep[j + 1]) {
149
125
            j++;
150
125
        }
151
477
        if (j == _value_sep_len - 1) {
152
55
            curpos = i - _value_sep_len + 1;
153
154
            /*
155
             * column_separator : "xx"
156
             * data.csv :  data1xxxxdata2
157
             *
158
             * Parse incorrectly:
159
             *      data1[xx]xxdata2
160
             *      data1x[xx]xdata2
161
             *      data1xx[xx]data2
162
             * The string "xxxx" is parsed into three "xx" delimiters.
163
             *
164
             * Parse correctly:
165
             *      data1[xx]xxdata2
166
             *      data1xx[xx]data2
167
             */
168
169
55
            if (curpos >= start) {
170
55
                process_value_func(line.data, start, curpos - start, _trimming_char,
171
55
                                   splitted_values);
172
55
                start = i + 1;
173
55
            }
174
175
55
            j = next[j];
176
55
        }
177
477
    }
178
9
    process_value_func(line.data, start, line.size - start, _trimming_char, splitted_values);
179
9
}
180
181
859
void PlainCsvTextFieldSplitter::do_split(const Slice& line, std::vector<Slice>* splitted_values) {
182
859
    if (is_single_char_delim) {
183
850
        _split_field_single_char(line, splitted_values);
184
850
    } else {
185
9
        _split_field_multi_char(line, splitted_values);
186
9
    }
187
859
}
188
189
CsvReader::CsvReader(RuntimeState* state, RuntimeProfile* profile, ScannerCounter* counter,
190
                     const TFileScanRangeParams& params, const TFileRangeDesc& range,
191
                     const std::vector<SlotDescriptor*>& file_slot_descs, size_t batch_size,
192
                     io::IOContext* io_ctx, std::shared_ptr<io::IOContext> io_ctx_holder)
193
1.02k
        : _profile(profile),
194
1.02k
          _params(params),
195
1.02k
          _file_reader(nullptr),
196
1.02k
          _line_reader(nullptr),
197
1.02k
          _decompressor(nullptr),
198
1.02k
          _state(state),
199
1.02k
          _counter(counter),
200
1.02k
          _range(range),
201
1.02k
          _file_slot_descs(file_slot_descs),
202
1.02k
          _line_reader_eof(false),
203
1.02k
          _skip_lines(0),
204
1.02k
          _io_ctx(io_ctx),
205
1.02k
          _io_ctx_holder(std::move(io_ctx_holder)),
206
1.02k
          _batch_size(std::max(batch_size, 1UL)) {
207
1.02k
    if (_io_ctx == nullptr && _io_ctx_holder) {
208
18
        _io_ctx = _io_ctx_holder.get();
209
18
    }
210
1.02k
    _file_format_type = _params.format_type;
211
1.02k
    _is_proto_format = _file_format_type == TFileFormatType::FORMAT_PROTO;
212
1.02k
    if (_range.__isset.compress_type) {
213
        // for compatibility
214
424
        _file_compress_type = _range.compress_type;
215
602
    } else {
216
602
        _file_compress_type = _params.compress_type;
217
602
    }
218
1.02k
    _size = _range.size;
219
220
1.02k
    _split_values.reserve(_file_slot_descs.size());
221
1.02k
    _init_system_properties();
222
1.02k
    _init_file_description();
223
1.02k
    _serdes = create_data_type_serdes(_file_slot_descs);
224
1.02k
}
225
226
1.02k
void CsvReader::_init_system_properties() {
227
1.02k
    if (_range.__isset.file_type) {
228
        // for compatibility
229
424
        _system_properties.system_type = _range.file_type;
230
602
    } else {
231
602
        _system_properties.system_type = _params.file_type;
232
602
    }
233
1.02k
    _system_properties.properties = _params.properties;
234
1.02k
    _system_properties.azure_client_config = io::azure_client_config_of(_params);
235
1.02k
    _system_properties.hdfs_params = _params.hdfs_params;
236
1.02k
    if (_params.__isset.broker_addresses) {
237
0
        _system_properties.broker_addresses.assign(_params.broker_addresses.begin(),
238
0
                                                   _params.broker_addresses.end());
239
0
    }
240
1.02k
}
241
242
1.02k
void CsvReader::_init_file_description() {
243
1.02k
    _file_description.path = _range.path;
244
1.02k
    _file_description.file_size = _range.__isset.file_size ? _range.file_size : -1;
245
1.02k
    if (_range.__isset.fs_name) {
246
0
        _file_description.fs_name = _range.fs_name;
247
0
    }
248
1.02k
    if (_range.__isset.file_cache_admission) {
249
16
        _file_description.file_cache_admission = _range.file_cache_admission;
250
16
    }
251
1.02k
}
252
253
598
Status CsvReader::init_reader(bool is_load) {
254
    // set the skip lines and start offset
255
598
    _start_offset = _range.start_offset;
256
598
    if (_start_offset == 0) {
257
        // check header typer first
258
228
        if (_params.__isset.file_attributes && _params.file_attributes.__isset.header_type &&
259
228
            !_params.file_attributes.header_type.empty()) {
260
4
            std::string header_type = to_lower(_params.file_attributes.header_type);
261
4
            if (header_type == BeConsts::CSV_WITH_NAMES) {
262
2
                _skip_lines = 1;
263
2
            } else if (header_type == BeConsts::CSV_WITH_NAMES_AND_TYPES) {
264
2
                _skip_lines = 2;
265
2
            }
266
224
        } else if (_params.file_attributes.__isset.skip_lines) {
267
2
            _skip_lines = _params.file_attributes.skip_lines;
268
2
        }
269
370
    } else if (_start_offset != 0) {
270
370
        if ((_file_compress_type != TFileCompressType::PLAIN) ||
271
370
            (_file_compress_type == TFileCompressType::UNKNOWN &&
272
370
             _file_format_type != TFileFormatType::FORMAT_CSV_PLAIN)) {
273
0
            return Status::InternalError<false>("For now we do not support split compressed file");
274
0
        }
275
        // pre-read to promise first line skipped always read
276
370
        int64_t pre_read_len = std::min(
277
370
                static_cast<int64_t>(_params.file_attributes.text_params.line_delimiter.size()),
278
370
                _start_offset);
279
370
        _start_offset -= pre_read_len;
280
370
        _size += pre_read_len;
281
        // not first range will always skip one line
282
370
        _skip_lines = 1;
283
370
    }
284
285
598
    _use_nullable_string_opt.resize(_file_slot_descs.size());
286
1.19k
    for (int i = 0; i < _file_slot_descs.size(); ++i) {
287
598
        auto data_type_ptr = _file_slot_descs[i]->type();
288
598
        if (data_type_ptr->is_nullable() && is_string_type(data_type_ptr->get_primitive_type())) {
289
598
            _use_nullable_string_opt[i] = 1;
290
598
        }
291
598
    }
292
293
598
    RETURN_IF_ERROR(_init_options());
294
598
    RETURN_IF_ERROR(_create_file_reader(false));
295
598
    RETURN_IF_ERROR(_create_decompressor());
296
598
    RETURN_IF_ERROR(_create_line_reader());
297
298
598
    _is_load = is_load;
299
598
    if (!_is_load) {
300
        // For query task, there are 2 slot mapping.
301
        // One is from file slot to values in line.
302
        //      eg, the file_slot_descs is k1, k3, k5, and values in line are k1, k2, k3, k4, k5
303
        //      the _col_idxs will save: 0, 2, 4
304
        // The other is from file slot to columns in output block
305
        //      eg, the file_slot_descs is k1, k3, k5, and columns in block are p1, k1, k3, k5
306
        //      where "p1" is the partition col which does not exist in file
307
        //      the _file_slot_idx_map will save: 1, 2, 3
308
0
        DCHECK(_params.__isset.column_idxs);
309
0
        _col_idxs = _params.column_idxs;
310
0
        int idx = 0;
311
0
        for (const auto& slot_info : _params.required_slots) {
312
0
            if (slot_info.is_file_slot) {
313
0
                _file_slot_idx_map.push_back(idx);
314
0
            }
315
0
            idx++;
316
0
        }
317
598
    } else {
318
        // For load task, the column order is same as file column order
319
598
        int i = 0;
320
598
        for (const auto& desc [[maybe_unused]] : _file_slot_descs) {
321
598
            _col_idxs.push_back(i++);
322
598
        }
323
598
    }
324
325
598
    _line_reader_eof = false;
326
598
    return Status::OK();
327
598
}
328
329
// ---- Unified init_reader(ReaderInitContext*) overrides ----
330
331
18
Status CsvReader::_open_file_reader(ReaderInitContext* base_ctx) {
332
18
    _start_offset = _range.start_offset;
333
18
    if (_start_offset == 0) {
334
18
        if (_params.__isset.file_attributes && _params.file_attributes.__isset.header_type &&
335
18
            !_params.file_attributes.header_type.empty()) {
336
0
            std::string header_type = to_lower(_params.file_attributes.header_type);
337
0
            if (header_type == BeConsts::CSV_WITH_NAMES) {
338
0
                _skip_lines = 1;
339
0
            } else if (header_type == BeConsts::CSV_WITH_NAMES_AND_TYPES) {
340
0
                _skip_lines = 2;
341
0
            }
342
18
        } else if (_params.file_attributes.__isset.skip_lines) {
343
16
            _skip_lines = _params.file_attributes.skip_lines;
344
16
        }
345
18
    } else if (_start_offset != 0) {
346
0
        if ((_file_compress_type != TFileCompressType::PLAIN) ||
347
0
            (_file_compress_type == TFileCompressType::UNKNOWN &&
348
0
             _file_format_type != TFileFormatType::FORMAT_CSV_PLAIN)) {
349
0
            return Status::InternalError<false>("For now we do not support split compressed file");
350
0
        }
351
0
        int64_t pre_read_len = std::min(
352
0
                static_cast<int64_t>(_params.file_attributes.text_params.line_delimiter.size()),
353
0
                _start_offset);
354
0
        _start_offset -= pre_read_len;
355
0
        _size += pre_read_len;
356
0
        _skip_lines = 1;
357
0
    }
358
359
18
    RETURN_IF_ERROR(_init_options());
360
18
    RETURN_IF_ERROR(_create_file_reader(false));
361
18
    return Status::OK();
362
18
}
363
364
18
Status CsvReader::_do_init_reader(ReaderInitContext* base_ctx) {
365
18
    auto* ctx = checked_context_cast<CsvInitContext>(base_ctx);
366
18
    _is_load = ctx->is_load;
367
368
18
    _use_nullable_string_opt.resize(_file_slot_descs.size());
369
42
    for (int i = 0; i < _file_slot_descs.size(); ++i) {
370
24
        auto data_type_ptr = _file_slot_descs[i]->type();
371
24
        if (data_type_ptr->is_nullable() && is_string_type(data_type_ptr->get_primitive_type())) {
372
0
            _use_nullable_string_opt[i] = 1;
373
0
        }
374
24
    }
375
376
18
    RETURN_IF_ERROR(_create_decompressor());
377
18
    RETURN_IF_ERROR(_create_line_reader());
378
379
18
    if (!_is_load) {
380
18
        DCHECK(_params.__isset.column_idxs);
381
18
        _col_idxs = _params.column_idxs;
382
18
        int idx = 0;
383
30
        for (const auto& slot_info : _params.required_slots) {
384
30
            if (slot_info.is_file_slot) {
385
30
                _file_slot_idx_map.push_back(idx);
386
30
            }
387
30
            idx++;
388
30
        }
389
18
    } else {
390
0
        int i = 0;
391
0
        for (const auto& desc [[maybe_unused]] : _file_slot_descs) {
392
0
            _col_idxs.push_back(i++);
393
0
        }
394
0
    }
395
18
    _line_reader_eof = false;
396
18
    return Status::OK();
397
18
}
398
399
39
void CsvReader::set_batch_size(size_t batch_size) {
400
    // 0 means "not set" / "use default" for the row-based readers; we must
401
    // never let _batch_size be 0 because _do_get_next_block uses it as the
402
    // upper bound of a `while (rows < _batch_size)` loop and a 0 would make
403
    // the reader return empty blocks and incorrectly signal EOF.
404
39
    _batch_size = std::max(batch_size, 1UL);
405
39
}
406
407
// !FIXME: Here we should use MutableBlock
408
1.10k
Status CsvReader::_do_get_next_block(Block* block, size_t* read_rows, bool* eof) {
409
1.10k
    if (_line_reader_eof) {
410
284
        *eof = true;
411
284
        return Status::OK();
412
284
    }
413
414
824
    const size_t batch_size = _batch_size;
415
824
    const auto max_block_bytes = _state->preferred_block_size_bytes();
416
824
    size_t rows = 0;
417
418
824
    bool success = false;
419
824
    bool is_remove_bom = false;
420
824
    if (_range.start_offset != 0 && _skip_lines > 0 && _enclose == 0 &&
421
824
        _file_format_type == TFileFormatType::FORMAT_CSV_PLAIN) {
422
370
        auto* text_reader = assert_cast<NewPlainTextLineReader*>(_line_reader.get());
423
370
        RETURN_IF_ERROR(text_reader->skip_split_prefix(_range.start_offset, _line_delimiter,
424
370
                                                       &_line_reader_eof, _io_ctx));
425
370
        _skip_lines = 0;
426
370
        is_remove_bom = true;
427
370
    }
428
824
    if (_push_down_agg_type == TPushAggOp::type::COUNT) {
429
1.04k
        while (rows < batch_size && !_line_reader_eof) {
430
646
            const uint8_t* ptr = nullptr;
431
646
            size_t size = 0;
432
646
            RETURN_IF_ERROR(_line_reader->read_line(&ptr, &size, &_line_reader_eof, _io_ctx));
433
434
            // _skip_lines == 0 means this line is the actual data beginning line for the entire file
435
            // is_remove_bom means _remove_bom should only execute once
436
646
            if (_skip_lines == 0 && !is_remove_bom) {
437
215
                ptr = _remove_bom(ptr, size);
438
215
                is_remove_bom = true;
439
215
            }
440
441
            // _skip_lines > 0 means we do not need to remove bom
442
646
            if (_skip_lines > 0) {
443
5
                _skip_lines--;
444
5
                is_remove_bom = true;
445
5
                continue;
446
5
            }
447
641
            if (size == 0) {
448
299
                if (!_line_reader_eof) {
449
0
                    if (_empty_line_as_record() || _state->is_read_csv_empty_line_as_null()) {
450
0
                        ++rows;
451
0
                    }
452
0
                }
453
                // Read empty line, continue
454
299
                continue;
455
299
            }
456
457
342
            RETURN_IF_ERROR(_validate_line(Slice(ptr, size), &success));
458
342
            ++rows;
459
342
        }
460
403
        auto mutable_columns_guard = block->mutate_columns_scoped();
461
403
        auto& mutate_columns = mutable_columns_guard.mutable_columns();
462
403
        for (auto& col : mutate_columns) {
463
403
            col->resize(rows);
464
403
        }
465
421
    } else {
466
421
        auto columns_guard = block->mutate_columns_scoped();
467
421
        auto& columns = columns_guard.mutable_columns();
468
421
        _reserve_nullable_string_columns(columns, batch_size);
469
1.19k
        while (rows < batch_size && !_line_reader_eof &&
470
1.19k
               (columns_byte_size(columns) < max_block_bytes)) {
471
778
            const uint8_t* ptr = nullptr;
472
778
            size_t size = 0;
473
778
            RETURN_IF_ERROR(_line_reader->read_line(&ptr, &size, &_line_reader_eof, _io_ctx));
474
475
            // _skip_lines == 0 means this line is the actual data beginning line for the entire file
476
            // is_remove_bom means _remove_bom should only execute once
477
776
            if (!is_remove_bom && _skip_lines == 0) {
478
231
                ptr = _remove_bom(ptr, size);
479
231
                is_remove_bom = true;
480
231
            }
481
482
            // _skip_lines > 0 means we do not remove bom
483
776
            if (_skip_lines > 0) {
484
5
                _skip_lines--;
485
5
                is_remove_bom = true;
486
5
                continue;
487
5
            }
488
771
            if (size == 0) {
489
315
                if (!_line_reader_eof) {
490
0
                    if (_empty_line_as_record()) {
491
0
                        Slice empty_line("", 0);
492
0
                        RETURN_IF_ERROR(_validate_line(empty_line, &success));
493
0
                        if (success) {
494
0
                            RETURN_IF_ERROR(_fill_dest_columns(empty_line, columns, &rows));
495
0
                        }
496
0
                    } else if (_state->is_read_csv_empty_line_as_null()) {
497
0
                        RETURN_IF_ERROR(_fill_empty_line(columns, &rows));
498
0
                    }
499
0
                }
500
                // Read empty line, continue
501
315
                continue;
502
315
            }
503
504
456
            RETURN_IF_ERROR(_validate_line(Slice(ptr, size), &success));
505
456
            if (!success) {
506
0
                continue;
507
0
            }
508
456
            RETURN_IF_ERROR(_fill_dest_columns(Slice(ptr, size), columns, &rows));
509
456
        }
510
421
    }
511
512
822
    *eof = (rows == 0);
513
822
    *read_rows = rows;
514
515
822
    return Status::OK();
516
824
}
517
518
18
Status CsvReader::_get_columns_impl(std::unordered_map<std::string, DataTypePtr>* name_to_type) {
519
24
    for (const auto& slot : _file_slot_descs) {
520
24
        name_to_type->emplace(slot->col_name(), slot->type());
521
24
    }
522
18
    return Status::OK();
523
18
}
524
525
// init decompressor, file reader and line reader for parsing schema
526
408
Status CsvReader::init_schema_reader() {
527
408
    _start_offset = _range.start_offset;
528
408
    if (_start_offset != 0) {
529
0
        return Status::InvalidArgument(
530
0
                "start offset of TFileRangeDesc must be zero in get parsered schema");
531
0
    }
532
408
    if (_params.file_type == TFileType::FILE_BROKER) {
533
0
        return Status::InternalError<false>(
534
0
                "Getting parsered schema from csv file do not support stream load and broker "
535
0
                "load.");
536
0
    }
537
538
    // csv file without names line and types line.
539
408
    _read_line = 1;
540
408
    _is_parse_name = false;
541
542
408
    if (_params.__isset.file_attributes && _params.file_attributes.__isset.header_type &&
543
408
        !_params.file_attributes.header_type.empty()) {
544
48
        std::string header_type = to_lower(_params.file_attributes.header_type);
545
48
        if (header_type == BeConsts::CSV_WITH_NAMES) {
546
25
            _is_parse_name = true;
547
25
        } else if (header_type == BeConsts::CSV_WITH_NAMES_AND_TYPES) {
548
23
            _read_line = 2;
549
23
            _is_parse_name = true;
550
23
        }
551
48
    }
552
553
408
    RETURN_IF_ERROR(_init_options());
554
408
    RETURN_IF_ERROR(_create_file_reader(true));
555
408
    RETURN_IF_ERROR(_create_decompressor());
556
408
    RETURN_IF_ERROR(_create_line_reader());
557
408
    return Status::OK();
558
408
}
559
560
Status CsvReader::get_parsed_schema(std::vector<std::string>* col_names,
561
408
                                    std::vector<DataTypePtr>* col_types) {
562
408
    if (_read_line == 1) {
563
385
        if (!_is_parse_name) { //parse csv file without names and types
564
360
            size_t col_nums = 0;
565
360
            RETURN_IF_ERROR(_parse_col_nums(&col_nums));
566
2.99k
            for (size_t i = 0; i < col_nums; ++i) {
567
2.63k
                col_names->emplace_back("c" + std::to_string(i + 1));
568
2.63k
            }
569
359
        } else { // parse csv file with names
570
25
            RETURN_IF_ERROR(_parse_col_names(col_names));
571
25
        }
572
573
3.11k
        for (size_t j = 0; j < col_names->size(); ++j) {
574
2.73k
            col_types->emplace_back(
575
2.73k
                    DataTypeFactory::instance().create_data_type(PrimitiveType::TYPE_STRING, true));
576
2.73k
        }
577
384
    } else { // parse csv file with names and types
578
23
        RETURN_IF_ERROR(_parse_col_names(col_names));
579
23
        RETURN_IF_ERROR(_parse_col_types(col_names->size(), col_types));
580
23
    }
581
407
    return Status::OK();
582
408
}
583
584
342
Status CsvReader::_deserialize_nullable_string(IColumn& column, Slice& slice) {
585
    // This is the per-row per-column hot path of CSV load (load reads every column as
586
    // nullable string). The column type was already verified by the checked assert_cast
587
    // in _reserve_nullable_string_columns at the beginning of the batch, so the casts
588
    // here can skip the release-build typeid check.
589
342
    auto& null_column = assert_cast<ColumnNullable&, TypeCheckOnRelease::DISABLE>(column);
590
342
    auto& string_column = assert_cast<ColumnString&, TypeCheckOnRelease::DISABLE>(
591
342
            null_column.get_nested_column());
592
342
    if (_empty_field_as_null && slice.size == 0) {
593
0
        string_column.insert_default();
594
0
        null_column.get_null_map_data().push_back(1);
595
0
        return Status::OK();
596
0
    }
597
342
    if (_options.null_len > 0 && !(_options.converted_from_string && slice.trim_double_quotes())) {
598
342
        if (slice.compare(Slice(_options.null_format, _options.null_len)) == 0) {
599
0
            string_column.insert_default();
600
0
            null_column.get_null_map_data().push_back(1);
601
0
            return Status::OK();
602
0
        }
603
342
    }
604
    // Same as DataTypeStringSerDe::deserialize_one_cell_from_csv (which never fails),
605
    // written out here to skip the SerDe layer and its per-cell assert_cast.
606
342
    if (_options.escape_char != 0) {
607
0
        escape_string_for_csv(slice.data, &slice.size, _options.escape_char, _options.quote_char);
608
0
    }
609
342
    string_column.insert_data(slice.data, slice.size);
610
342
    null_column.get_null_map_data().push_back(0);
611
342
    return Status::OK();
612
342
}
613
614
1.02k
Status CsvReader::_init_options() {
615
    // get column_separator and line_delimiter
616
1.02k
    _value_separator = _params.file_attributes.text_params.column_separator;
617
1.02k
    _value_separator_length = _value_separator.size();
618
1.02k
    _line_delimiter = _params.file_attributes.text_params.line_delimiter;
619
1.02k
    _line_delimiter_length = _line_delimiter.size();
620
1.02k
    if (_params.file_attributes.text_params.__isset.enclose) {
621
424
        _enclose = _params.file_attributes.text_params.enclose;
622
424
    }
623
1.02k
    if (_params.file_attributes.text_params.__isset.escape) {
624
424
        _escape = _params.file_attributes.text_params.escape;
625
424
    }
626
627
1.02k
    _trim_tailing_spaces =
628
1.02k
            (_state != nullptr && _state->trim_tailing_spaces_for_external_table_query());
629
630
1.02k
    _options.escape_char = _escape;
631
1.02k
    _options.quote_char = _enclose;
632
633
1.02k
    if (_params.file_attributes.text_params.collection_delimiter.empty()) {
634
1.02k
        _options.collection_delim = ',';
635
1.02k
    } else {
636
0
        _options.collection_delim = _params.file_attributes.text_params.collection_delimiter[0];
637
0
    }
638
1.02k
    if (_params.file_attributes.text_params.mapkv_delimiter.empty()) {
639
1.02k
        _options.map_key_delim = ':';
640
1.02k
    } else {
641
0
        _options.map_key_delim = _params.file_attributes.text_params.mapkv_delimiter[0];
642
0
    }
643
644
1.02k
    if (_params.file_attributes.text_params.__isset.null_format) {
645
0
        _options.null_format = _params.file_attributes.text_params.null_format.data();
646
0
        _options.null_len = _params.file_attributes.text_params.null_format.length();
647
0
    }
648
649
1.02k
    if (_params.file_attributes.__isset.trim_double_quotes) {
650
424
        _trim_double_quotes = _params.file_attributes.trim_double_quotes;
651
424
    }
652
1.02k
    _options.converted_from_string = _trim_double_quotes;
653
654
1.02k
    if (_state != nullptr) {
655
616
        _keep_cr = _state->query_options().keep_carriage_return;
656
616
    }
657
658
1.02k
    if (_params.file_attributes.text_params.__isset.empty_field_as_null) {
659
424
        _empty_field_as_null = _params.file_attributes.text_params.empty_field_as_null;
660
424
    }
661
1.02k
    return Status::OK();
662
1.02k
}
663
664
1.02k
Status CsvReader::_create_decompressor() {
665
1.02k
    if (_file_compress_type != TFileCompressType::UNKNOWN) {
666
1.02k
        RETURN_IF_ERROR(Decompressor::create_decompressor(_file_compress_type, &_decompressor));
667
1.02k
    } else {
668
2
        RETURN_IF_ERROR(Decompressor::create_decompressor(_file_format_type, &_decompressor));
669
2
    }
670
671
1.02k
    return Status::OK();
672
1.02k
}
673
674
1.02k
Status CsvReader::_create_file_reader(bool need_schema) {
675
1.02k
    if (_params.file_type == TFileType::FILE_STREAM) {
676
130
        RETURN_IF_ERROR(FileFactory::create_pipe_reader(_range.load_id, &_file_reader, _state,
677
130
                                                        need_schema));
678
894
    } else {
679
894
        _file_description.mtime = _range.__isset.modification_time ? _range.modification_time : 0;
680
894
        io::FileReaderOptions reader_options = FileFactory::get_reader_options(
681
894
                _state ? _state->query_options() : _default_query_options, _file_description);
682
894
        io::FileReaderSPtr file_reader;
683
894
        if (_io_ctx_holder) {
684
296
            file_reader = DORIS_TRY(io::DelegateReader::create_file_reader(
685
296
                    _profile, _system_properties, _file_description, reader_options,
686
296
                    io::DelegateReader::AccessMode::SEQUENTIAL,
687
296
                    std::static_pointer_cast<const io::IOContext>(_io_ctx_holder),
688
296
                    io::PrefetchRange(_range.start_offset, _range.start_offset + _range.size)));
689
598
        } else {
690
598
            file_reader = DORIS_TRY(io::DelegateReader::create_file_reader(
691
598
                    _profile, _system_properties, _file_description, reader_options,
692
598
                    io::DelegateReader::AccessMode::SEQUENTIAL, _io_ctx,
693
598
                    io::PrefetchRange(_range.start_offset, _range.start_offset + _range.size)));
694
598
        }
695
894
        _file_reader = _io_ctx && _io_ctx->file_reader_stats
696
894
                               ? std::make_shared<io::TracingFileReader>(std::move(file_reader),
697
296
                                                                         _io_ctx->file_reader_stats)
698
894
                               : file_reader;
699
894
    }
700
1.02k
    if (_file_reader->size() == 0 && _params.file_type != TFileType::FILE_STREAM &&
701
1.02k
        _params.file_type != TFileType::FILE_BROKER) {
702
0
        return Status::EndOfFile("init reader failed, empty csv file: " + _range.path);
703
0
    }
704
1.02k
    return Status::OK();
705
1.02k
}
706
707
1.02k
Status CsvReader::_create_line_reader() {
708
1.02k
    std::shared_ptr<TextLineReaderContextIf> text_line_reader_ctx;
709
1.02k
    if (_enclose == 0) {
710
1.02k
        text_line_reader_ctx = std::make_shared<PlainTextLineReaderCtx>(
711
1.02k
                _line_delimiter, _line_delimiter_length, _keep_cr);
712
1.02k
        _fields_splitter = std::make_unique<PlainCsvTextFieldSplitter>(
713
1.02k
                _trim_tailing_spaces, false, _value_separator, _value_separator_length, -1);
714
715
1.02k
    } else {
716
        // in load task, the _file_slot_descs is empty vector, so we need to set col_sep_num to 0
717
4
        size_t col_sep_num = _file_slot_descs.size() > 1 ? _file_slot_descs.size() - 1 : 0;
718
4
        _enclose_reader_ctx = std::make_shared<EncloseCsvLineReaderCtx>(
719
4
                _line_delimiter, _line_delimiter_length, _value_separator, _value_separator_length,
720
4
                col_sep_num, _enclose, _escape, _keep_cr);
721
4
        text_line_reader_ctx = _enclose_reader_ctx;
722
723
4
        _fields_splitter = std::make_unique<EncloseCsvTextFieldSplitter>(
724
4
                _trim_tailing_spaces, true, _enclose_reader_ctx, _value_separator_length, _enclose);
725
4
    }
726
1.02k
    switch (_file_format_type) {
727
1.02k
    case TFileFormatType::FORMAT_CSV_PLAIN:
728
1.02k
        [[fallthrough]];
729
1.02k
    case TFileFormatType::FORMAT_CSV_GZ:
730
1.02k
        [[fallthrough]];
731
1.02k
    case TFileFormatType::FORMAT_CSV_BZ2:
732
1.02k
        [[fallthrough]];
733
1.02k
    case TFileFormatType::FORMAT_CSV_LZ4FRAME:
734
1.02k
        [[fallthrough]];
735
1.02k
    case TFileFormatType::FORMAT_CSV_LZ4BLOCK:
736
1.02k
        [[fallthrough]];
737
1.02k
    case TFileFormatType::FORMAT_CSV_LZOP:
738
1.02k
        [[fallthrough]];
739
1.02k
    case TFileFormatType::FORMAT_CSV_SNAPPYBLOCK:
740
1.02k
        [[fallthrough]];
741
1.02k
    case TFileFormatType::FORMAT_CSV_DEFLATE:
742
1.02k
        _line_reader =
743
1.02k
                NewPlainTextLineReader::create_unique(_profile, _file_reader, _decompressor.get(),
744
1.02k
                                                      text_line_reader_ctx, _size, _start_offset);
745
746
1.02k
        break;
747
0
    case TFileFormatType::FORMAT_PROTO:
748
0
        _fields_splitter = std::make_unique<CsvProtoFieldSplitter>();
749
0
        _line_reader = NewPlainBinaryLineReader::create_unique(_file_reader);
750
0
        break;
751
0
    default:
752
0
        return Status::InternalError<false>(
753
0
                "Unknown format type, cannot init line reader in csv reader, type={}",
754
0
                _file_format_type);
755
1.02k
    }
756
1.02k
    return Status::OK();
757
1.02k
}
758
759
164
Status CsvReader::_deserialize_one_cell(DataTypeSerDeSPtr serde, IColumn& column, Slice& slice) {
760
164
    return serde->deserialize_one_cell_from_csv(column, slice, _options);
761
164
}
762
763
Status CsvReader::_fill_dest_columns(const Slice& line, std::vector<MutableColumnPtr>& columns,
764
456
                                     size_t* rows) {
765
456
    bool is_success = false;
766
767
456
    RETURN_IF_ERROR(_line_split_to_values(line, &is_success));
768
456
    if (UNLIKELY(!is_success)) {
769
        // If not success, which means we met an invalid row, filter this row and return.
770
0
        return Status::OK();
771
0
    }
772
773
962
    for (int i = 0; i < _file_slot_descs.size(); ++i) {
774
506
        int col_idx = _col_idxs[i];
775
        // col idx is out of range, fill with null format
776
506
        auto value = col_idx < _split_values.size()
777
506
                             ? _split_values[col_idx]
778
506
                             : Slice(_options.null_format, _options.null_len);
779
780
506
        IColumn* col_ptr = columns[i].get();
781
506
        if (!_is_load) {
782
164
            col_ptr = columns[_file_slot_idx_map[i]].get();
783
164
        }
784
785
506
        if (_use_nullable_string_opt[i]) {
786
            // For load task, we always read "string" from file.
787
            // So serdes[i] here must be DataTypeNullableSerDe, and DataTypeNullableSerDe -> nested_serde must be DataTypeStringSerDe.
788
            // So we use deserialize_nullable_string and stringSerDe to reduce virtual function calls.
789
342
            RETURN_IF_ERROR(_deserialize_nullable_string(*col_ptr, value));
790
342
        } else {
791
164
            RETURN_IF_ERROR(_deserialize_one_cell(_serdes[i], *col_ptr, value));
792
164
        }
793
506
    }
794
456
    ++(*rows);
795
796
456
    return Status::OK();
797
456
}
798
799
void CsvReader::_reserve_nullable_string_columns(std::vector<MutableColumnPtr>& columns,
800
421
                                                 size_t batch_size) {
801
848
    for (int i = 0; i < _file_slot_descs.size(); ++i) {
802
427
        if (!_use_nullable_string_opt[i]) {
803
24
            continue;
804
24
        }
805
403
        IColumn* col_ptr = _is_load ? columns[i].get() : columns[_file_slot_idx_map[i]].get();
806
        // The checked casts here (once per batch) guarantee the column types for the
807
        // unchecked per-row casts in _deserialize_nullable_string.
808
403
        auto& null_column = assert_cast<ColumnNullable&>(*col_ptr);
809
403
        auto& string_column = assert_cast<ColumnString&>(null_column.get_nested_column());
810
        // Reserve up front so the per-row loop does not pay for incremental growth.
811
        // The string chars are not reserved because their total size is unpredictable.
812
403
        string_column.get_offsets().reserve(string_column.size() + batch_size);
813
403
        null_column.get_null_map_data().reserve(null_column.get_null_map_data().size() +
814
403
                                                batch_size);
815
403
    }
816
421
}
817
818
0
Status CsvReader::_fill_empty_line(std::vector<MutableColumnPtr>& columns, size_t* rows) {
819
0
    for (int i = 0; i < _file_slot_descs.size(); ++i) {
820
0
        IColumn* col_ptr = columns[i].get();
821
0
        if (!_is_load) {
822
0
            col_ptr = columns[_file_slot_idx_map[i]].get();
823
0
        }
824
0
        auto& null_column = assert_cast<ColumnNullable&>(*col_ptr);
825
0
        null_column.insert_data(nullptr, 0);
826
0
    }
827
0
    ++(*rows);
828
0
    return Status::OK();
829
0
}
830
831
798
Status CsvReader::_validate_line(const Slice& line, bool* success) {
832
798
    if (!_is_proto_format && !validate_utf8(_params, line.data, line.size)) {
833
0
        if (!_is_load) {
834
0
            return Status::InternalError<false>("Only support csv data in utf8 codec");
835
0
        } else {
836
0
            _counter->num_rows_filtered++;
837
0
            *success = false;
838
0
            RETURN_IF_ERROR(_state->append_error_msg_to_file(
839
0
                    [&]() -> std::string { return std::string(line.data, line.size); },
840
0
                    [&]() -> std::string {
841
0
                        return "Invalid file encoding: all CSV files must be UTF-8 encoded";
842
0
                    }));
843
0
            return Status::OK();
844
0
        }
845
0
    }
846
798
    *success = true;
847
798
    return Status::OK();
848
798
}
849
850
456
Status CsvReader::_line_split_to_values(const Slice& line, bool* success) {
851
456
    _split_line(line);
852
853
456
    if (_is_load) {
854
        // Only check for load task. For query task, the non exist column will be filled "null".
855
        // if actual column number in csv file is not equal to _file_slot_descs.size()
856
        // then filter this line.
857
342
        bool ignore_col = false;
858
342
        ignore_col = _params.__isset.file_attributes &&
859
342
                     _params.file_attributes.__isset.ignore_csv_redundant_col &&
860
342
                     _params.file_attributes.ignore_csv_redundant_col;
861
862
342
        if ((!ignore_col && _split_values.size() != _file_slot_descs.size()) ||
863
342
            (ignore_col && _split_values.size() < _file_slot_descs.size())) {
864
0
            _counter->num_rows_filtered++;
865
0
            *success = false;
866
0
            RETURN_IF_ERROR(_state->append_error_msg_to_file(
867
0
                    [&]() -> std::string { return std::string(line.data, line.size); },
868
0
                    [&]() -> std::string {
869
0
                        fmt::memory_buffer error_msg;
870
0
                        fmt::format_to(error_msg,
871
0
                                       "Column count mismatch: expected {}, but found {}",
872
0
                                       _file_slot_descs.size(), _split_values.size());
873
0
                        std::string escaped_separator =
874
0
                                std::regex_replace(_value_separator, std::regex("\t"), "\\t");
875
0
                        std::string escaped_delimiter =
876
0
                                std::regex_replace(_line_delimiter, std::regex("\n"), "\\n");
877
0
                        fmt::format_to(error_msg, " (sep:{} delim:{}", escaped_separator,
878
0
                                       escaped_delimiter);
879
0
                        if (_enclose != 0) {
880
0
                            fmt::format_to(error_msg, " encl:{}", _enclose);
881
0
                        }
882
0
                        if (_escape != 0) {
883
0
                            fmt::format_to(error_msg, " esc:{}", _escape);
884
0
                        }
885
0
                        fmt::format_to(error_msg, ")");
886
0
                        return fmt::to_string(error_msg);
887
0
                    }));
888
0
            return Status::OK();
889
0
        }
890
342
    }
891
892
456
    *success = true;
893
456
    return Status::OK();
894
456
}
895
896
863
void CsvReader::_split_line(const Slice& line) {
897
863
    _split_values.clear();
898
863
    _fields_splitter->split_line(line, &_split_values);
899
863
}
900
901
360
Status CsvReader::_parse_col_nums(size_t* col_nums) {
902
360
    const uint8_t* ptr = nullptr;
903
360
    size_t size = 0;
904
360
    RETURN_IF_ERROR(_line_reader->read_line(&ptr, &size, &_line_reader_eof, _io_ctx));
905
360
    if (size == 0) {
906
0
        return Status::InternalError<false>(
907
0
                "The first line is empty, can not parse column numbers");
908
0
    }
909
360
    if (!validate_utf8(_params, reinterpret_cast<const char*>(ptr), size)) {
910
1
        return Status::InternalError<false>("Only support csv data in utf8 codec");
911
1
    }
912
359
    ptr = _remove_bom(ptr, size);
913
359
    _split_line(Slice(ptr, size));
914
359
    *col_nums = _split_values.size();
915
359
    return Status::OK();
916
360
}
917
918
48
Status CsvReader::_parse_col_names(std::vector<std::string>* col_names) {
919
48
    const uint8_t* ptr = nullptr;
920
48
    size_t size = 0;
921
    // no use of _line_reader_eof
922
48
    RETURN_IF_ERROR(_line_reader->read_line(&ptr, &size, &_line_reader_eof, _io_ctx));
923
48
    if (size == 0) {
924
0
        return Status::InternalError<false>("The first line is empty, can not parse column names");
925
0
    }
926
48
    if (!validate_utf8(_params, reinterpret_cast<const char*>(ptr), size)) {
927
0
        return Status::InternalError<false>("Only support csv data in utf8 codec");
928
0
    }
929
48
    ptr = _remove_bom(ptr, size);
930
48
    _split_line(Slice(ptr, size));
931
187
    for (auto _split_value : _split_values) {
932
187
        col_names->emplace_back(_split_value.to_string());
933
187
    }
934
48
    return Status::OK();
935
48
}
936
937
// TODO(ftw): parse type
938
23
Status CsvReader::_parse_col_types(size_t col_nums, std::vector<DataTypePtr>* col_types) {
939
    // delete after.
940
113
    for (size_t i = 0; i < col_nums; ++i) {
941
90
        col_types->emplace_back(make_nullable(std::make_shared<DataTypeString>()));
942
90
    }
943
944
    // 1. check _line_reader_eof
945
    // 2. read line
946
    // 3. check utf8
947
    // 4. check size
948
    // 5. check _split_values.size must equal to col_nums.
949
    // 6. fill col_types
950
23
    return Status::OK();
951
23
}
952
953
853
const uint8_t* CsvReader::_remove_bom(const uint8_t* ptr, size_t& size) {
954
853
    if (size >= 3 && ptr[0] == 0xEF && ptr[1] == 0xBB && ptr[2] == 0xBF) {
955
4
        LOG(INFO) << "remove bom";
956
4
        constexpr size_t bom_size = 3;
957
4
        size -= bom_size;
958
        // In enclose mode, column_sep_positions were computed on the original line
959
        // (including BOM). After shifting the pointer, we must adjust those positions
960
        // so they remain correct relative to the new start.
961
4
        if (_enclose_reader_ctx) {
962
0
            _enclose_reader_ctx->adjust_column_sep_positions(bom_size);
963
0
        }
964
4
        return ptr + bom_size;
965
4
    }
966
849
    return ptr;
967
853
}
968
969
18
Status CsvReader::close() {
970
18
    if (_line_reader) {
971
18
        _line_reader->close();
972
18
    }
973
974
18
    if (_file_reader) {
975
18
        RETURN_IF_ERROR(_file_reader->close());
976
18
    }
977
978
18
    return Status::OK();
979
18
}
980
981
} // namespace doris