Coverage Report

Created: 2026-08-06 19:00

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
be/src/service/http/action/stream_load.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 "service/http/action/stream_load.h"
19
20
// use string iequal
21
#include <event2/buffer.h>
22
#include <event2/http.h>
23
#include <gen_cpp/FrontendService.h>
24
#include <gen_cpp/FrontendService_types.h>
25
#include <gen_cpp/HeartbeatService_types.h>
26
#include <gen_cpp/PaloInternalService_types.h>
27
#include <gen_cpp/PlanNodes_types.h>
28
#include <gen_cpp/Types_types.h>
29
#include <sys/time.h>
30
#include <thrift/protocol/TDebugProtocol.h>
31
32
#include <algorithm>
33
#include <cstdint>
34
#include <cstdlib>
35
#include <ctime>
36
#include <functional>
37
#include <future>
38
#include <sstream>
39
#include <stdexcept>
40
#include <utility>
41
42
#include "cloud/config.h"
43
#include "common/config.h"
44
#include "common/consts.h"
45
#include "common/logging.h"
46
#include "common/metrics/doris_metrics.h"
47
#include "common/metrics/metrics.h"
48
#include "common/status.h"
49
#include "common/utils.h"
50
#include "io/fs/stream_load_pipe.h"
51
#include "load/group_commit/group_commit_mgr.h"
52
#include "load/load_path_mgr.h"
53
#include "load/message_body_sink.h"
54
#include "load/stream_load/new_load_stream_mgr.h"
55
#include "load/stream_load/stream_load_context.h"
56
#include "load/stream_load/stream_load_executor.h"
57
#include "load/stream_load/stream_load_recorder.h"
58
#include "runtime/cluster_info.h"
59
#include "runtime/exec_env.h"
60
#include "service/http/http_channel.h"
61
#include "service/http/http_common.h"
62
#include "service/http/http_headers.h"
63
#include "service/http/http_request.h"
64
#include "service/http/utils.h"
65
#include "storage/storage_engine.h"
66
#include "util/byte_buffer.h"
67
#include "util/client_cache.h"
68
#include "util/load_util.h"
69
#include "util/string_util.h"
70
#include "util/thrift_rpc_helper.h"
71
#include "util/time.h"
72
#include "util/uid_util.h"
73
#include "util/url_coding.h"
74
75
namespace doris {
76
using namespace ErrorCode;
77
78
DEFINE_COUNTER_METRIC_PROTOTYPE_2ARG(streaming_load_requests_total, MetricUnit::REQUESTS);
79
DEFINE_COUNTER_METRIC_PROTOTYPE_2ARG(streaming_load_duration_ms, MetricUnit::MILLISECONDS);
80
DEFINE_GAUGE_METRIC_PROTOTYPE_2ARG(streaming_load_current_processing, MetricUnit::REQUESTS);
81
82
bvar::LatencyRecorder g_stream_load_receive_data_latency_ms("stream_load_receive_data_latency_ms");
83
bvar::LatencyRecorder g_stream_load_commit_and_publish_latency_ms("stream_load",
84
                                                                  "commit_and_publish_ms");
85
86
static constexpr size_t MIN_CHUNK_SIZE = 64 * 1024;
87
static constexpr size_t MEBIBYTE = 1024 * 1024;
88
static const std::string CHUNK = "chunked";
89
static const std::string OFF_MODE = "off_mode";
90
static const std::string SYNC_MODE = "sync_mode";
91
static const std::string ASYNC_MODE = "async_mode";
92
93
#ifdef BE_TEST
94
TStreamLoadPutResult k_stream_load_put_result;
95
#endif
96
97
StreamLoadAction::StreamLoadAction(ExecEnv* exec_env)
98
10
        : HttpHandlerWithAuth(exec_env, TPrivilegeHier::GLOBAL, TPrivilegeType::LOAD) {
99
    // Use LOAD privilege type: requires LOAD permission
100
10
    _stream_load_entity =
101
10
            DorisMetrics::instance()->metric_registry()->register_entity("stream_load");
102
10
    INT_COUNTER_METRIC_REGISTER(_stream_load_entity, streaming_load_requests_total);
103
10
    INT_COUNTER_METRIC_REGISTER(_stream_load_entity, streaming_load_duration_ms);
104
10
    INT_GAUGE_METRIC_REGISTER(_stream_load_entity, streaming_load_current_processing);
105
10
}
106
107
6
StreamLoadAction::~StreamLoadAction() {
108
6
    DorisMetrics::instance()->metric_registry()->deregister_entity(_stream_load_entity);
109
6
}
110
111
5.19k
void StreamLoadAction::handle(HttpRequest* req) {
112
5.19k
    std::shared_ptr<StreamLoadContext> ctx =
113
5.19k
            std::static_pointer_cast<StreamLoadContext>(req->handler_ctx());
114
5.19k
    if (ctx == nullptr) {
115
0
        return;
116
0
    }
117
118
5.19k
    {
119
5.19k
        std::unique_lock<std::mutex> lock1(ctx->_send_reply_lock);
120
5.19k
        ctx->_can_send_reply = true;
121
5.19k
        ctx->_can_send_reply_cv.notify_all();
122
5.19k
    }
123
124
    // status already set to fail
125
5.19k
    if (ctx->status.ok()) {
126
5.18k
        ctx->status = _handle(ctx, req);
127
5.18k
        if (!ctx->status.ok() && !ctx->status.is<PUBLISH_TIMEOUT>()) {
128
0
            _send_reply(ctx, req);
129
0
        }
130
5.18k
    }
131
5.19k
}
132
133
5.18k
Status StreamLoadAction::_handle(std::shared_ptr<StreamLoadContext> ctx, HttpRequest* req) {
134
5.18k
    if (ctx->body_bytes > 0 && ctx->receive_bytes != ctx->body_bytes) {
135
0
        LOG(WARNING) << "recevie body don't equal with body bytes, body_bytes=" << ctx->body_bytes
136
0
                     << ", receive_bytes=" << ctx->receive_bytes << ", id=" << ctx->id;
137
0
        return Status::Error<ErrorCode::NETWORK_ERROR>("receive body don't equal with body bytes");
138
0
    }
139
140
    // if we use non-streaming, MessageBodyFileSink.finish will close the file
141
5.18k
    RETURN_IF_ERROR(ctx->body_sink->finish());
142
5.18k
    if (!ctx->use_streaming) {
143
        // we need to close file first, then execute_plan_fragment here
144
21
        ctx->body_sink.reset();
145
21
        TPipelineFragmentParamsList mocked;
146
21
        RETURN_IF_ERROR(_exec_env->stream_load_executor()->execute_plan_fragment(
147
21
                ctx, mocked,
148
21
                [req, this](std::shared_ptr<StreamLoadContext> ctx) { _on_finish(ctx, req); }));
149
21
    }
150
151
5.18k
    return Status::OK();
152
5.18k
}
153
154
5.18k
void StreamLoadAction::_on_finish(std::shared_ptr<StreamLoadContext> ctx, HttpRequest* req) {
155
5.18k
    ctx->status = ctx->load_status_future.get();
156
5.18k
    if (ctx->status.ok()) {
157
4.86k
        if (ctx->group_commit) {
158
421
            LOG(INFO) << "skip commit because this is group commit, pipe_id="
159
421
                      << ctx->id.to_string();
160
4.44k
        } else if (ctx->two_phase_commit) {
161
32
            int64_t pre_commit_start_time = MonotonicNanos();
162
32
            ctx->status = _exec_env->stream_load_executor()->pre_commit_txn(ctx.get());
163
32
            ctx->pre_commit_txn_cost_nanos = MonotonicNanos() - pre_commit_start_time;
164
4.40k
        } else {
165
            // If put file success we need commit this load
166
4.40k
            int64_t commit_and_publish_start_time = MonotonicNanos();
167
4.40k
            ctx->status = _exec_env->stream_load_executor()->commit_txn(ctx.get());
168
4.40k
            ctx->commit_and_publish_txn_cost_nanos =
169
4.40k
                    MonotonicNanos() - commit_and_publish_start_time;
170
4.40k
            g_stream_load_commit_and_publish_latency_ms
171
4.40k
                    << ctx->commit_and_publish_txn_cost_nanos / 1000000;
172
4.40k
        }
173
4.86k
    }
174
5.18k
    _send_reply(ctx, req);
175
5.18k
}
176
177
5.37k
void StreamLoadAction::_send_reply(std::shared_ptr<StreamLoadContext> ctx, HttpRequest* req) {
178
5.37k
    std::unique_lock<std::mutex> lock1(ctx->_send_reply_lock);
179
    // 1. _can_send_reply: ensure `send_reply` is invoked only after on_header/handle complete,
180
    //    avoid client errors (e.g., broken pipe).
181
    // 2. _finish_send_reply: Prevent duplicate reply sending; skip reply if HTTP request is canceled
182
    //    due to long import execution time.
183
5.37k
    while (!ctx->_finish_send_reply && !ctx->_can_send_reply) {
184
3
        ctx->_can_send_reply_cv.wait(lock1);
185
3
    }
186
5.37k
    if (ctx->_finish_send_reply) {
187
0
        return;
188
0
    }
189
5.37k
    DCHECK(ctx->_can_send_reply);
190
5.37k
    ctx->_finish_send_reply = true;
191
5.37k
    ctx->_can_send_reply_cv.notify_all();
192
5.37k
    ctx->load_cost_millis = UnixMillis() - ctx->start_millis;
193
194
5.37k
    if (!ctx->status.ok() && !ctx->status.is<PUBLISH_TIMEOUT>()) {
195
507
        LOG(WARNING) << "handle streaming load failed, id=" << ctx->id
196
507
                     << ", errmsg=" << ctx->status;
197
507
        if (ctx->need_rollback) {
198
476
            _exec_env->stream_load_executor()->rollback_txn(ctx.get());
199
476
            ctx->need_rollback = false;
200
476
        }
201
507
        if (ctx->body_sink != nullptr) {
202
476
            ctx->body_sink->cancel(ctx->status.to_string());
203
476
        }
204
507
    }
205
206
5.37k
    auto str = ctx->to_json();
207
    // add new line at end
208
5.37k
    str = str + '\n';
209
210
5.37k
#ifndef BE_TEST
211
5.37k
    if (config::enable_stream_load_record || config::enable_stream_load_record_to_audit_log_table) {
212
5.36k
        if (req->header(HTTP_SKIP_RECORD_TO_AUDIT_LOG_TABLE).empty()) {
213
5.13k
            str = ctx->prepare_stream_load_record(str);
214
5.13k
            _save_stream_load_record(ctx, str);
215
5.13k
        }
216
5.36k
    }
217
5.37k
#endif
218
219
5.37k
    HttpChannel::send_reply(req, str);
220
221
5.37k
    LOG(INFO) << "finished to execute stream load. label=" << ctx->label
222
5.37k
              << ", txn_id=" << ctx->txn_id << ", query_id=" << ctx->id
223
5.37k
              << ", load_cost_ms=" << ctx->load_cost_millis << ", receive_data_cost_ms="
224
5.37k
              << (ctx->receive_and_read_data_cost_nanos - ctx->read_data_cost_nanos) / 1000000
225
5.37k
              << ", read_data_cost_ms=" << ctx->read_data_cost_nanos / 1000000
226
5.37k
              << ", write_data_cost_ms=" << ctx->write_data_cost_nanos / 1000000
227
5.37k
              << ", commit_and_publish_txn_cost_ms="
228
5.37k
              << ctx->commit_and_publish_txn_cost_nanos / 1000000
229
5.37k
              << ", number_total_rows=" << ctx->number_total_rows
230
5.37k
              << ", number_loaded_rows=" << ctx->number_loaded_rows
231
5.37k
              << ", receive_bytes=" << ctx->receive_bytes << ", loaded_bytes=" << ctx->loaded_bytes
232
5.37k
              << ", error_url=" << ctx->error_url;
233
234
    // update statistics
235
5.37k
    streaming_load_requests_total->increment(1);
236
5.37k
    streaming_load_duration_ms->increment(ctx->load_cost_millis);
237
5.37k
    if (!ctx->data_saved_path.empty()) {
238
22
        _exec_env->load_path_mgr()->clean_tmp_files(ctx->data_saved_path);
239
22
    }
240
5.37k
}
241
242
5.37k
int StreamLoadAction::on_header(HttpRequest* req) {
243
    // Call parent's auth check first
244
5.37k
    int ret = HttpHandlerWithAuth::on_header(req);
245
5.37k
    if (ret != 0) {
246
0
        return ret; // Auth failed, return error
247
0
    }
248
249
    // Continue with stream load specific header processing
250
5.37k
    req->mark_send_reply();
251
252
5.37k
    streaming_load_current_processing->increment(1);
253
254
5.37k
    std::shared_ptr<StreamLoadContext> ctx = std::make_shared<StreamLoadContext>(_exec_env);
255
5.37k
    req->set_handler_ctx(ctx);
256
257
5.37k
    ctx->load_type = TLoadType::MANUL_LOAD;
258
5.37k
    ctx->load_src_type = TLoadSourceType::RAW;
259
260
5.37k
    url_decode(req->param(HTTP_DB_KEY), &ctx->db);
261
5.37k
    url_decode(req->param(HTTP_TABLE_KEY), &ctx->table);
262
5.37k
    ctx->label = req->header(HTTP_LABEL_KEY);
263
5.37k
    ctx->two_phase_commit = req->header(HTTP_TWO_PHASE_COMMIT) == "true";
264
5.37k
    Status st = _handle_group_commit(req, ctx);
265
5.37k
    if (!ctx->group_commit && ctx->label.empty()) {
266
310
        ctx->label = generate_uuid_string();
267
310
    }
268
269
5.37k
    LOG(INFO) << "new income streaming load request." << ctx->brief() << ", db=" << ctx->db
270
5.37k
              << ", tbl=" << ctx->table << ", group_commit=" << ctx->group_commit
271
5.37k
              << ", group_commit_mode=" << ctx->group_commit_mode
272
5.37k
              << ", HTTP headers=" << req->get_all_headers();
273
5.37k
    ctx->begin_receive_and_read_data_cost_nanos = MonotonicNanos();
274
275
5.37k
    if (st.ok()) {
276
5.37k
        st = _on_header(req, ctx);
277
5.37k
        LOG(INFO) << "finished to handle HTTP header, " << ctx->brief();
278
5.37k
    }
279
5.37k
    if (!st.ok()) {
280
180
        ctx->status = std::move(st);
281
180
        {
282
180
            std::unique_lock<std::mutex> lock1(ctx->_send_reply_lock);
283
180
            ctx->_can_send_reply = true;
284
180
            ctx->_can_send_reply_cv.notify_all();
285
180
        }
286
180
        _send_reply(ctx, req);
287
180
        return -1;
288
180
    }
289
5.19k
    return 0;
290
5.37k
}
291
292
5.37k
Status StreamLoadAction::_on_header(HttpRequest* http_req, std::shared_ptr<StreamLoadContext> ctx) {
293
    // auth information
294
5.37k
    if (!parse_basic_auth(*http_req, &ctx->auth)) {
295
0
        LOG(WARNING) << "parse basic authorization failed." << ctx->brief();
296
0
        return Status::NotAuthorized("no valid Basic authorization");
297
0
    }
298
299
    // get format of this put
300
5.37k
    std::string format_str = http_req->header(HTTP_FORMAT_KEY);
301
5.37k
    if (iequal(format_str, BeConsts::CSV_WITH_NAMES) ||
302
5.37k
        iequal(format_str, BeConsts::CSV_WITH_NAMES_AND_TYPES)) {
303
12
        ctx->header_type = format_str;
304
        //treat as CSV
305
12
        format_str = BeConsts::CSV;
306
12
    }
307
5.37k
    LoadUtil::parse_format(format_str, http_req->header(HTTP_COMPRESS_TYPE), &ctx->format,
308
5.37k
                           &ctx->compress_type);
309
5.37k
    if (ctx->format == TFileFormatType::FORMAT_UNKNOWN) {
310
1
        return Status::Error<ErrorCode::DATA_FILE_TYPE_ERROR>("unknown data format, format={}",
311
1
                                                              http_req->header(HTTP_FORMAT_KEY));
312
1
    }
313
314
    // check content length
315
5.37k
    ctx->body_bytes = 0;
316
5.37k
    const auto csv_max_body_mb = config::streaming_load_max_mb;
317
5.37k
    size_t csv_max_body_bytes = csv_max_body_mb * MEBIBYTE;
318
5.37k
    const auto json_max_body_mb = config::streaming_load_json_max_mb;
319
5.37k
    size_t json_max_body_bytes = json_max_body_mb * MEBIBYTE;
320
5.37k
    bool read_json_by_line = false;
321
5.37k
    if (!http_req->header(HTTP_READ_JSON_BY_LINE).empty()) {
322
3.02k
        if (iequal(http_req->header(HTTP_READ_JSON_BY_LINE), "true")) {
323
3.02k
            read_json_by_line = true;
324
3.02k
        }
325
3.02k
    }
326
5.37k
    if (!http_req->header(HttpHeaders::CONTENT_LENGTH).empty()) {
327
5.08k
        try {
328
5.08k
            ctx->body_bytes = std::stol(http_req->header(HttpHeaders::CONTENT_LENGTH));
329
5.08k
        } catch (const std::exception& e) {
330
0
            return Status::InvalidArgument("invalid HTTP header CONTENT_LENGTH={}: {}",
331
0
                                           http_req->header(HttpHeaders::CONTENT_LENGTH), e.what());
332
0
        }
333
        // json max body size
334
5.08k
        if ((ctx->format == TFileFormatType::FORMAT_JSON) &&
335
5.08k
            (ctx->body_bytes > json_max_body_bytes) && !read_json_by_line) {
336
1
            return Status::Error<ErrorCode::EXCEEDED_LIMIT>(
337
1
                    "json body size {} bytes ({:.2f} MiB) exceeds the limit of {} bytes ({} MiB) "
338
1
                    "set by BE's conf streaming_load_json_max_mb. Increase it if you are sure "
339
1
                    "this load is reasonable",
340
1
                    ctx->body_bytes, static_cast<double>(ctx->body_bytes) / MEBIBYTE,
341
1
                    json_max_body_bytes, json_max_body_mb);
342
1
        }
343
        // csv max body size
344
5.08k
        else if (ctx->body_bytes > csv_max_body_bytes) {
345
1
            LOG(WARNING) << "body exceed max size." << ctx->brief();
346
1
            return Status::Error<ErrorCode::EXCEEDED_LIMIT>(
347
1
                    "body size {} bytes ({:.2f} MiB) exceeds the limit of {} bytes ({} MiB) set "
348
1
                    "by BE's conf streaming_load_max_mb. Increase it if you are sure this load is "
349
1
                    "reasonable",
350
1
                    ctx->body_bytes, static_cast<double>(ctx->body_bytes) / MEBIBYTE,
351
1
                    csv_max_body_bytes, csv_max_body_mb);
352
1
        }
353
5.08k
    } else {
354
286
#ifndef BE_TEST
355
286
        evhttp_connection_set_max_body_size(
356
286
                evhttp_request_get_connection(http_req->get_evhttp_request()), csv_max_body_bytes);
357
286
#endif
358
286
    }
359
360
5.37k
    if (!http_req->header(HttpHeaders::TRANSFER_ENCODING).empty()) {
361
285
        if (http_req->header(HttpHeaders::TRANSFER_ENCODING).find(CHUNK) != std::string::npos) {
362
285
            ctx->is_chunked_transfer = true;
363
285
        }
364
285
    }
365
5.37k
    if (UNLIKELY((http_req->header(HttpHeaders::CONTENT_LENGTH).empty() &&
366
5.37k
                  !ctx->is_chunked_transfer))) {
367
1
        LOG(WARNING) << "content_length is empty and transfer-encoding!=chunked, please set "
368
1
                        "content_length or transfer-encoding=chunked";
369
1
        return Status::InvalidArgument(
370
1
                "content_length is empty and transfer-encoding!=chunked, please set content_length "
371
1
                "or transfer-encoding=chunked");
372
5.37k
    } else if (UNLIKELY(!http_req->header(HttpHeaders::CONTENT_LENGTH).empty() &&
373
5.37k
                        ctx->is_chunked_transfer)) {
374
1
        LOG(WARNING) << "please do not set both content_length and transfer-encoding";
375
1
        return Status::InvalidArgument(
376
1
                "please do not set both content_length and transfer-encoding");
377
1
    }
378
379
5.36k
    if (!http_req->header(HTTP_TIMEOUT).empty()) {
380
254
        ctx->timeout_second = DORIS_TRY(safe_stoi(http_req->header(HTTP_TIMEOUT), HTTP_TIMEOUT));
381
253
    }
382
5.36k
    if (!http_req->header(HTTP_COMMENT).empty()) {
383
1
        ctx->load_comment = http_req->header(HTTP_COMMENT);
384
1
    }
385
    // begin transaction
386
5.36k
    if (!ctx->group_commit) {
387
4.94k
        int64_t begin_txn_start_time = MonotonicNanos();
388
4.94k
        RETURN_IF_ERROR(_exec_env->stream_load_executor()->begin_txn(ctx.get()));
389
4.91k
        ctx->begin_txn_cost_nanos = MonotonicNanos() - begin_txn_start_time;
390
4.91k
        if (ctx->group_commit) {
391
2
            RETURN_IF_ERROR(_check_wal_space(ctx->group_commit_mode, ctx->body_bytes));
392
2
        }
393
4.91k
    }
394
395
    // process put file
396
5.34k
    return _process_put(http_req, ctx);
397
5.36k
}
398
399
319k
void StreamLoadAction::on_chunk_data(HttpRequest* req) {
400
319k
    std::shared_ptr<StreamLoadContext> ctx =
401
319k
            std::static_pointer_cast<StreamLoadContext>(req->handler_ctx());
402
319k
    if (ctx == nullptr || !ctx->status.ok()) {
403
20.8k
        return;
404
20.8k
    }
405
406
298k
    struct evhttp_request* ev_req = req->get_evhttp_request();
407
298k
    auto evbuf = evhttp_request_get_input_buffer(ev_req);
408
409
298k
    SCOPED_ATTACH_TASK(ExecEnv::GetInstance()->stream_load_pipe_tracker());
410
411
298k
    int64_t start_read_data_time = MonotonicNanos();
412
596k
    while (evbuffer_get_length(evbuf) > 0) {
413
298k
        ByteBufferPtr bb;
414
298k
        Status st = ByteBuffer::allocate(128 * 1024, &bb);
415
298k
        if (!st.ok()) {
416
0
            ctx->status = st;
417
0
            return;
418
0
        }
419
298k
        auto remove_bytes = evbuffer_remove(evbuf, bb->ptr, bb->capacity);
420
298k
        bb->pos = remove_bytes;
421
298k
        bb->flip();
422
298k
        st = ctx->body_sink->append(bb);
423
298k
        if (!st.ok()) {
424
3
            LOG(WARNING) << "append body content failed. errmsg=" << st << ", " << ctx->brief();
425
3
            ctx->status = st;
426
3
            return;
427
3
        }
428
298k
        ctx->receive_bytes += remove_bytes;
429
298k
    }
430
298k
    int64_t read_data_time = MonotonicNanos() - start_read_data_time;
431
298k
    int64_t last_receive_and_read_data_cost_nanos = ctx->receive_and_read_data_cost_nanos;
432
298k
    ctx->read_data_cost_nanos += read_data_time;
433
298k
    ctx->receive_and_read_data_cost_nanos =
434
298k
            MonotonicNanos() - ctx->begin_receive_and_read_data_cost_nanos;
435
298k
    g_stream_load_receive_data_latency_ms
436
298k
            << (ctx->receive_and_read_data_cost_nanos - last_receive_and_read_data_cost_nanos -
437
298k
                read_data_time) /
438
298k
                       1000000;
439
298k
}
440
441
5.37k
void StreamLoadAction::free_handler_ctx(std::shared_ptr<void> param) {
442
5.37k
    std::shared_ptr<StreamLoadContext> ctx = std::static_pointer_cast<StreamLoadContext>(param);
443
5.37k
    if (ctx == nullptr) {
444
0
        return;
445
0
    }
446
    // sender is gone, make receiver know it
447
5.37k
    if (ctx->body_sink != nullptr) {
448
5.32k
        ctx->body_sink->cancel("sender is gone");
449
5.32k
    }
450
    // remove stream load context from stream load manager and the resource will be released
451
5.37k
    ctx->exec_env()->new_load_stream_mgr()->remove(ctx->id);
452
5.37k
    streaming_load_current_processing->increment(-1);
453
5.37k
}
454
455
Status StreamLoadAction::_process_put(HttpRequest* http_req,
456
5.34k
                                      std::shared_ptr<StreamLoadContext> ctx) {
457
    // Now we use stream
458
5.34k
    ctx->use_streaming = LoadUtil::is_format_support_streaming(ctx->format);
459
460
    // put request
461
5.34k
    TStreamLoadPutRequest request;
462
5.34k
    set_request_auth(&request, ctx->auth);
463
5.34k
    request.db = ctx->db;
464
5.34k
    request.tbl = ctx->table;
465
5.34k
    request.txnId = ctx->txn_id;
466
5.34k
    request.formatType = ctx->format;
467
5.34k
    request.__set_compress_type(ctx->compress_type);
468
5.34k
    request.__set_header_type(ctx->header_type);
469
5.34k
    request.__set_loadId(ctx->id.to_thrift());
470
5.34k
    if (ctx->use_streaming) {
471
5.31k
        std::shared_ptr<io::StreamLoadPipe> pipe;
472
5.31k
        if (ctx->is_chunked_transfer) {
473
284
            pipe = std::make_shared<io::StreamLoadPipe>(
474
284
                    io::kMaxPipeBufferedBytes /* max_buffered_bytes */);
475
284
            pipe->set_is_chunked_transfer(true);
476
5.03k
        } else {
477
5.03k
            pipe = std::make_shared<io::StreamLoadPipe>(
478
5.03k
                    io::kMaxPipeBufferedBytes /* max_buffered_bytes */,
479
5.03k
                    MIN_CHUNK_SIZE /* min_chunk_size */, ctx->body_bytes /* total_length */);
480
5.03k
        }
481
5.31k
        request.fileType = TFileType::FILE_STREAM;
482
5.31k
        ctx->body_sink = pipe;
483
5.31k
        ctx->pipe = pipe;
484
5.31k
        RETURN_IF_ERROR(_exec_env->new_load_stream_mgr()->put(ctx->id, ctx));
485
5.31k
    } else {
486
24
        RETURN_IF_ERROR(_data_saved_path(http_req, &request.path, ctx->body_bytes));
487
24
        auto file_sink = std::make_shared<MessageBodyFileSink>(request.path);
488
24
        RETURN_IF_ERROR(file_sink->open());
489
24
        request.__isset.path = true;
490
24
        request.fileType = TFileType::FILE_LOCAL;
491
24
        request.__set_file_size(ctx->body_bytes);
492
24
        ctx->body_sink = file_sink;
493
24
        ctx->data_saved_path = request.path;
494
24
    }
495
5.34k
    if (!http_req->header(HTTP_COLUMNS).empty()) {
496
1.20k
        request.__set_columns(http_req->header(HTTP_COLUMNS));
497
1.20k
    }
498
5.34k
    if (!http_req->header(HTTP_WHERE).empty()) {
499
10
        request.__set_where(http_req->header(HTTP_WHERE));
500
10
    }
501
5.34k
    if (!http_req->header(HTTP_COLUMN_SEPARATOR).empty()) {
502
2.00k
        request.__set_columnSeparator(http_req->header(HTTP_COLUMN_SEPARATOR));
503
2.00k
    }
504
5.34k
    if (!http_req->header(HTTP_LINE_DELIMITER).empty()) {
505
210
        request.__set_line_delimiter(http_req->header(HTTP_LINE_DELIMITER));
506
210
    }
507
5.34k
    if (!http_req->header(HTTP_ENCLOSE).empty() && !http_req->header(HTTP_ENCLOSE).empty()) {
508
80
        const auto& enclose_str = http_req->header(HTTP_ENCLOSE);
509
80
        if (enclose_str.length() != 1) {
510
0
            return Status::InvalidArgument("enclose must be single-char, actually is {}",
511
0
                                           enclose_str);
512
0
        }
513
80
        request.__set_enclose(http_req->header(HTTP_ENCLOSE)[0]);
514
80
    }
515
5.34k
    if (!http_req->header(HTTP_ESCAPE).empty() && !http_req->header(HTTP_ESCAPE).empty()) {
516
80
        const auto& escape_str = http_req->header(HTTP_ESCAPE);
517
80
        if (escape_str.length() != 1) {
518
0
            return Status::InvalidArgument("escape must be single-char, actually is {}",
519
0
                                           escape_str);
520
0
        }
521
80
        request.__set_escape(http_req->header(HTTP_ESCAPE)[0]);
522
80
    }
523
5.34k
    if (!http_req->header(HTTP_PARTITIONS).empty()) {
524
15
        request.__set_partitions(http_req->header(HTTP_PARTITIONS));
525
15
        request.__set_isTempPartition(false);
526
15
        if (!http_req->header(HTTP_TEMP_PARTITIONS).empty()) {
527
0
            return Status::InvalidArgument(
528
0
                    "Can not specify both partitions and temporary partitions");
529
0
        }
530
15
    }
531
5.34k
    if (!http_req->header(HTTP_TEMP_PARTITIONS).empty()) {
532
1
        request.__set_partitions(http_req->header(HTTP_TEMP_PARTITIONS));
533
1
        request.__set_isTempPartition(true);
534
1
        if (!http_req->header(HTTP_PARTITIONS).empty()) {
535
0
            return Status::InvalidArgument(
536
0
                    "Can not specify both partitions and temporary partitions");
537
0
        }
538
1
    }
539
5.34k
    if (!http_req->header(HTTP_NEGATIVE).empty() && http_req->header(HTTP_NEGATIVE) == "true") {
540
0
        request.__set_negative(true);
541
5.34k
    } else {
542
5.34k
        request.__set_negative(false);
543
5.34k
    }
544
5.34k
    bool strictMode = false;
545
5.34k
    if (!http_req->header(HTTP_STRICT_MODE).empty()) {
546
350
        if (iequal(http_req->header(HTTP_STRICT_MODE), "false")) {
547
217
            strictMode = false;
548
217
        } else if (iequal(http_req->header(HTTP_STRICT_MODE), "true")) {
549
133
            strictMode = true;
550
133
        } else {
551
0
            return Status::InvalidArgument("Invalid strict mode format. Must be bool type");
552
0
        }
553
350
        request.__set_strictMode(strictMode);
554
350
    }
555
    // timezone first. if not, try system time_zone
556
5.34k
    if (!http_req->header(HTTP_TIMEZONE).empty()) {
557
17
        request.__set_timezone(http_req->header(HTTP_TIMEZONE));
558
5.32k
    } else if (!http_req->header(HTTP_TIME_ZONE).empty()) {
559
0
        request.__set_timezone(http_req->header(HTTP_TIME_ZONE));
560
0
    }
561
5.34k
    if (!http_req->header(HTTP_EXEC_MEM_LIMIT).empty()) {
562
22
        try {
563
22
            request.__set_execMemLimit(std::stoll(http_req->header(HTTP_EXEC_MEM_LIMIT)));
564
22
        } catch (const std::invalid_argument& e) {
565
7
            return Status::InvalidArgument("Invalid mem limit format, {}", e.what());
566
7
        }
567
22
    }
568
5.33k
    if (!http_req->header(HTTP_JSONPATHS).empty()) {
569
27
        request.__set_jsonpaths(http_req->header(HTTP_JSONPATHS));
570
27
    }
571
5.33k
    if (!http_req->header(HTTP_JSONROOT).empty()) {
572
7
        request.__set_json_root(http_req->header(HTTP_JSONROOT));
573
7
    }
574
5.33k
    if (!http_req->header(HTTP_STRIP_OUTER_ARRAY).empty()) {
575
79
        if (iequal(http_req->header(HTTP_STRIP_OUTER_ARRAY), "true")) {
576
73
            request.__set_strip_outer_array(true);
577
73
        } else {
578
6
            request.__set_strip_outer_array(false);
579
6
        }
580
5.25k
    } else {
581
5.25k
        request.__set_strip_outer_array(false);
582
5.25k
    }
583
584
5.33k
    if (!http_req->header(HTTP_READ_JSON_BY_LINE).empty()) {
585
3.02k
        if (iequal(http_req->header(HTTP_READ_JSON_BY_LINE), "true")) {
586
3.02k
            request.__set_read_json_by_line(true);
587
3.02k
        } else {
588
2
            request.__set_read_json_by_line(false);
589
2
        }
590
3.02k
    } else {
591
2.31k
        request.__set_read_json_by_line(false);
592
2.31k
    }
593
594
5.33k
    if (http_req->header(HTTP_READ_JSON_BY_LINE).empty() &&
595
5.33k
        http_req->header(HTTP_STRIP_OUTER_ARRAY).empty()) {
596
2.24k
        request.__set_read_json_by_line(true);
597
2.24k
        request.__set_strip_outer_array(false);
598
2.24k
    }
599
600
5.33k
    if (!http_req->header(HTTP_NUM_AS_STRING).empty()) {
601
2
        if (iequal(http_req->header(HTTP_NUM_AS_STRING), "true")) {
602
1
            request.__set_num_as_string(true);
603
1
        } else {
604
1
            request.__set_num_as_string(false);
605
1
        }
606
5.33k
    } else {
607
5.33k
        request.__set_num_as_string(false);
608
5.33k
    }
609
5.33k
    if (!http_req->header(HTTP_FUZZY_PARSE).empty()) {
610
9
        if (iequal(http_req->header(HTTP_FUZZY_PARSE), "true")) {
611
8
            request.__set_fuzzy_parse(true);
612
8
        } else {
613
1
            request.__set_fuzzy_parse(false);
614
1
        }
615
5.32k
    } else {
616
5.32k
        request.__set_fuzzy_parse(false);
617
5.32k
    }
618
619
5.33k
    if (!http_req->header(HTTP_FUNCTION_COLUMN + "." + HTTP_SEQUENCE_COL).empty()) {
620
90
        request.__set_sequence_col(
621
90
                http_req->header(HTTP_FUNCTION_COLUMN + "." + HTTP_SEQUENCE_COL));
622
90
    }
623
624
5.33k
    if (!http_req->header(HTTP_SEND_BATCH_PARALLELISM).empty()) {
625
4
        int parallelism = DORIS_TRY(safe_stoi(http_req->header(HTTP_SEND_BATCH_PARALLELISM),
626
2
                                              HTTP_SEND_BATCH_PARALLELISM));
627
2
        request.__set_send_batch_parallelism(parallelism);
628
2
    }
629
630
5.33k
    if (!http_req->header(HTTP_LOAD_TO_SINGLE_TABLET).empty()) {
631
8
        if (iequal(http_req->header(HTTP_LOAD_TO_SINGLE_TABLET), "true")) {
632
7
            request.__set_load_to_single_tablet(true);
633
7
        } else {
634
1
            request.__set_load_to_single_tablet(false);
635
1
        }
636
8
    }
637
638
5.33k
    if (ctx->timeout_second != -1) {
639
253
        request.__set_timeout(ctx->timeout_second);
640
253
    }
641
5.33k
    request.__set_thrift_rpc_timeout_ms(config::thrift_rpc_timeout_ms);
642
5.33k
    TMergeType::type merge_type = TMergeType::APPEND;
643
5.33k
    StringCaseMap<TMergeType::type> merge_type_map = {{"APPEND", TMergeType::APPEND},
644
5.33k
                                                      {"DELETE", TMergeType::DELETE},
645
5.33k
                                                      {"MERGE", TMergeType::MERGE}};
646
5.33k
    if (!http_req->header(HTTP_MERGE_TYPE).empty()) {
647
75
        std::string merge_type_str = http_req->header(HTTP_MERGE_TYPE);
648
75
        auto iter = merge_type_map.find(merge_type_str);
649
75
        if (iter != merge_type_map.end()) {
650
74
            merge_type = iter->second;
651
74
        } else {
652
1
            return Status::InvalidArgument("Invalid merge type {}", merge_type_str);
653
1
        }
654
74
        if (merge_type == TMergeType::MERGE && http_req->header(HTTP_DELETE_CONDITION).empty()) {
655
0
            return Status::InvalidArgument("Excepted DELETE ON clause when merge type is MERGE.");
656
74
        } else if (merge_type != TMergeType::MERGE &&
657
74
                   !http_req->header(HTTP_DELETE_CONDITION).empty()) {
658
0
            return Status::InvalidArgument(
659
0
                    "Not support DELETE ON clause when merge type is not MERGE.");
660
0
        }
661
74
    }
662
5.33k
    request.__set_merge_type(merge_type);
663
5.33k
    if (!http_req->header(HTTP_DELETE_CONDITION).empty()) {
664
17
        request.__set_delete_condition(http_req->header(HTTP_DELETE_CONDITION));
665
17
    }
666
667
5.33k
    if (!http_req->header(HTTP_MAX_FILTER_RATIO).empty()) {
668
418
        ctx->max_filter_ratio = strtod(http_req->header(HTTP_MAX_FILTER_RATIO).c_str(), nullptr);
669
418
        request.__set_max_filter_ratio(ctx->max_filter_ratio);
670
418
    }
671
672
5.33k
    if (!http_req->header(HTTP_HIDDEN_COLUMNS).empty()) {
673
2.70k
        request.__set_hidden_columns(http_req->header(HTTP_HIDDEN_COLUMNS));
674
2.70k
    }
675
5.33k
    if (!http_req->header(HTTP_TRIM_DOUBLE_QUOTES).empty()) {
676
90
        if (iequal(http_req->header(HTTP_TRIM_DOUBLE_QUOTES), "true")) {
677
26
            request.__set_trim_double_quotes(true);
678
64
        } else {
679
64
            request.__set_trim_double_quotes(false);
680
64
        }
681
90
    }
682
5.33k
    if (!http_req->header(HTTP_SKIP_LINES).empty()) {
683
81
        int skip_lines = DORIS_TRY(safe_stoi(http_req->header(HTTP_SKIP_LINES), HTTP_SKIP_LINES));
684
81
        if (skip_lines < 0) {
685
1
            return Status::InvalidArgument("Invalid 'skip_lines': {}", skip_lines);
686
1
        }
687
80
        request.__set_skip_lines(skip_lines);
688
80
    }
689
5.33k
    if (!http_req->header(HTTP_ENABLE_PROFILE).empty()) {
690
0
        if (iequal(http_req->header(HTTP_ENABLE_PROFILE), "true")) {
691
0
            request.__set_enable_profile(true);
692
0
        } else {
693
0
            request.__set_enable_profile(false);
694
0
        }
695
0
    }
696
697
5.33k
    if (!http_req->header(HTTP_UNIQUE_KEY_UPDATE_MODE).empty()) {
698
170
        static const StringCaseMap<TUniqueKeyUpdateMode::type> unique_key_update_mode_map = {
699
170
                {"UPSERT", TUniqueKeyUpdateMode::UPSERT},
700
170
                {"UPDATE_FIXED_COLUMNS", TUniqueKeyUpdateMode::UPDATE_FIXED_COLUMNS},
701
170
                {"UPDATE_FLEXIBLE_COLUMNS", TUniqueKeyUpdateMode::UPDATE_FLEXIBLE_COLUMNS}};
702
170
        std::string unique_key_update_mode_str = http_req->header(HTTP_UNIQUE_KEY_UPDATE_MODE);
703
170
        auto iter = unique_key_update_mode_map.find(unique_key_update_mode_str);
704
170
        if (iter != unique_key_update_mode_map.end()) {
705
169
            TUniqueKeyUpdateMode::type unique_key_update_mode = iter->second;
706
169
            if (unique_key_update_mode == TUniqueKeyUpdateMode::UPDATE_FLEXIBLE_COLUMNS) {
707
                // check constraints when flexible partial update is enabled
708
169
                if (ctx->format != TFileFormatType::FORMAT_JSON) {
709
1
                    return Status::InvalidArgument(
710
1
                            "flexible partial update only support json format as input file "
711
1
                            "currently");
712
1
                }
713
168
                if (!http_req->header(HTTP_FUZZY_PARSE).empty() &&
714
168
                    iequal(http_req->header(HTTP_FUZZY_PARSE), "true")) {
715
1
                    return Status::InvalidArgument(
716
1
                            "Don't support flexible partial update when 'fuzzy_parse' is enabled");
717
1
                }
718
167
                if (!http_req->header(HTTP_COLUMNS).empty()) {
719
1
                    return Status::InvalidArgument(
720
1
                            "Don't support flexible partial update when 'columns' is specified");
721
1
                }
722
166
                if (!http_req->header(HTTP_JSONPATHS).empty()) {
723
1
                    return Status::InvalidArgument(
724
1
                            "Don't support flexible partial update when 'jsonpaths' is specified");
725
1
                }
726
165
                if (!http_req->header(HTTP_HIDDEN_COLUMNS).empty()) {
727
1
                    return Status::InvalidArgument(
728
1
                            "Don't support flexible partial update when 'hidden_columns' is "
729
1
                            "specified");
730
1
                }
731
164
                if (!http_req->header(HTTP_FUNCTION_COLUMN + "." + HTTP_SEQUENCE_COL).empty()) {
732
0
                    return Status::InvalidArgument(
733
0
                            "Don't support flexible partial update when "
734
0
                            "'function_column.sequence_col' is specified");
735
0
                }
736
164
                if (!http_req->header(HTTP_MERGE_TYPE).empty()) {
737
1
                    return Status::InvalidArgument(
738
1
                            "Don't support flexible partial update when "
739
1
                            "'merge_type' is specified");
740
1
                }
741
163
                if (!http_req->header(HTTP_WHERE).empty()) {
742
1
                    return Status::InvalidArgument(
743
1
                            "Don't support flexible partial update when "
744
1
                            "'where' is specified");
745
1
                }
746
163
            }
747
162
            request.__set_unique_key_update_mode(unique_key_update_mode);
748
162
        } else {
749
1
            return Status::InvalidArgument(
750
1
                    "Invalid unique_key_partial_mode {}, must be one of 'UPSERT', "
751
1
                    "'UPDATE_FIXED_COLUMNS' or 'UPDATE_FLEXIBLE_COLUMNS'",
752
1
                    unique_key_update_mode_str);
753
1
        }
754
170
    }
755
756
5.32k
    if (http_req->header(HTTP_UNIQUE_KEY_UPDATE_MODE).empty() &&
757
5.32k
        !http_req->header(HTTP_PARTIAL_COLUMNS).empty()) {
758
        // only consider `partial_columns` parameter when `unique_key_update_mode` is not set
759
338
        if (iequal(http_req->header(HTTP_PARTIAL_COLUMNS), "true")) {
760
332
            request.__set_unique_key_update_mode(TUniqueKeyUpdateMode::UPDATE_FIXED_COLUMNS);
761
            // for backward compatibility
762
332
            request.__set_partial_update(true);
763
332
        }
764
338
    }
765
766
5.32k
    if (!http_req->header(HTTP_PARTIAL_UPDATE_NEW_ROW_POLICY).empty()) {
767
18
        static const std::map<std::string, TPartialUpdateNewRowPolicy::type> policy_map {
768
18
                {"APPEND", TPartialUpdateNewRowPolicy::APPEND},
769
18
                {"ERROR", TPartialUpdateNewRowPolicy::ERROR}};
770
771
18
        auto policy_name = http_req->header(HTTP_PARTIAL_UPDATE_NEW_ROW_POLICY);
772
18
        std::transform(policy_name.begin(), policy_name.end(), policy_name.begin(),
773
92
                       [](unsigned char c) { return std::toupper(c); });
774
18
        auto it = policy_map.find(policy_name);
775
18
        if (it == policy_map.end()) {
776
0
            return Status::InvalidArgument(
777
0
                    "Invalid partial_update_new_key_behavior {}, must be one of {'APPEND', "
778
0
                    "'ERROR'}",
779
0
                    policy_name);
780
0
        }
781
18
        request.__set_partial_update_new_key_policy(it->second);
782
18
    }
783
784
5.32k
    if (!http_req->header(HTTP_MEMTABLE_ON_SINKNODE).empty()) {
785
52
        bool value = iequal(http_req->header(HTTP_MEMTABLE_ON_SINKNODE), "true");
786
52
        request.__set_memtable_on_sink_node(value);
787
52
    }
788
5.32k
    if (!http_req->header(HTTP_LOAD_STREAM_PER_NODE).empty()) {
789
0
        int stream_per_node = DORIS_TRY(
790
0
                safe_stoi(http_req->header(HTTP_LOAD_STREAM_PER_NODE), HTTP_LOAD_STREAM_PER_NODE));
791
0
        request.__set_stream_per_node(stream_per_node);
792
0
    }
793
5.32k
    if (ctx->group_commit) {
794
424
        request.__set_group_commit_mode(ctx->group_commit_mode);
795
424
    }
796
797
    // Keep cloud_cluster for compatibility with old FEs during rolling upgrade. New FEs use
798
    // backend_id below to bind planning to the compute group of the receiving BE.
799
5.32k
    if (!http_req->header(HTTP_COMPUTE_GROUP).empty()) {
800
0
        request.__set_cloud_cluster(http_req->header(HTTP_COMPUTE_GROUP));
801
5.32k
    } else if (!http_req->header(HTTP_CLOUD_CLUSTER).empty()) {
802
66
        request.__set_cloud_cluster(http_req->header(HTTP_CLOUD_CLUSTER));
803
66
    }
804
805
5.32k
    if (_exec_env->cluster_info()->backend_id != 0) {
806
5.32k
        request.__set_backend_id(_exec_env->cluster_info()->backend_id);
807
5.32k
    } else {
808
3
        LOG(WARNING) << "_exec_env->cluster_info not set backend_id";
809
3
    }
810
811
5.32k
    if (!http_req->header(HTTP_EMPTY_FIELD_AS_NULL).empty()) {
812
1
        if (iequal(http_req->header(HTTP_EMPTY_FIELD_AS_NULL), "true")) {
813
1
            request.__set_empty_field_as_null(true);
814
1
        }
815
1
    }
816
817
5.32k
#ifndef BE_TEST
818
    // plan this load
819
5.32k
    TNetworkAddress master_addr = _exec_env->cluster_info()->master_fe_addr;
820
5.32k
    int64_t stream_load_put_start_time = MonotonicNanos();
821
5.32k
    RETURN_IF_ERROR(ThriftRpcHelper::rpc<FrontendServiceClient>(
822
5.32k
            master_addr.hostname, master_addr.port,
823
5.32k
            [&request, ctx](FrontendServiceConnection& client) {
824
5.32k
                client->streamLoadPut(ctx->put_result, request);
825
5.32k
            }));
826
5.32k
    ctx->stream_load_put_cost_nanos = MonotonicNanos() - stream_load_put_start_time;
827
#else
828
    ctx->put_result = k_stream_load_put_result;
829
#endif
830
5.32k
    Status plan_status(Status::create(ctx->put_result.status));
831
5.32k
    if (!plan_status.ok()) {
832
130
        LOG(WARNING) << "plan streaming load failed. errmsg=" << plan_status << ctx->brief();
833
130
        return plan_status;
834
130
    }
835
5.32k
    DCHECK(ctx->put_result.__isset.pipeline_params);
836
5.19k
    ctx->put_result.pipeline_params.query_options.__set_enable_strict_cast(false);
837
5.19k
    ctx->put_result.pipeline_params.query_options.__set_enable_insert_strict(strictMode);
838
5.19k
    if (config::is_cloud_mode() && ctx->two_phase_commit && ctx->is_mow_table()) {
839
1
        return Status::NotSupported("stream load 2pc is unsupported for mow table");
840
1
    }
841
5.19k
    if (iequal(ctx->group_commit_mode, ASYNC_MODE)) {
842
        // FIXME find a way to avoid chunked stream load write large WALs
843
422
        size_t content_length = 0;
844
422
        if (!http_req->header(HttpHeaders::CONTENT_LENGTH).empty()) {
845
420
            try {
846
420
                content_length = std::stol(http_req->header(HttpHeaders::CONTENT_LENGTH));
847
420
            } catch (const std::exception& e) {
848
0
                return Status::InvalidArgument("invalid HTTP header CONTENT_LENGTH={}: {}",
849
0
                                               http_req->header(HttpHeaders::CONTENT_LENGTH),
850
0
                                               e.what());
851
0
            }
852
420
            if (LoadUtil::is_compressed_load(ctx->compress_type, ctx->format)) {
853
5
                content_length *= 3;
854
5
            }
855
420
        }
856
422
        ctx->put_result.pipeline_params.__set_content_length(content_length);
857
422
    }
858
859
5.19k
    VLOG_NOTICE << "params is "
860
0
                << apache::thrift::ThriftDebugString(ctx->put_result.pipeline_params);
861
    // if we not use streaming, we must download total content before we begin
862
    // to process this load
863
5.19k
    if (!ctx->use_streaming) {
864
21
        return Status::OK();
865
21
    }
866
867
5.17k
    TPipelineFragmentParamsList mocked;
868
5.17k
    return _exec_env->stream_load_executor()->execute_plan_fragment(
869
5.17k
            ctx, mocked, [http_req, this](std::shared_ptr<StreamLoadContext> ctx) {
870
5.16k
                _on_finish(ctx, http_req);
871
5.16k
            });
872
5.19k
}
873
874
Status StreamLoadAction::_data_saved_path(HttpRequest* req, std::string* file_path,
875
22
                                          int64_t file_bytes) {
876
22
    std::string prefix;
877
22
    RETURN_IF_ERROR(_exec_env->load_path_mgr()->allocate_dir(req->param(HTTP_DB_KEY), "", &prefix,
878
22
                                                             file_bytes));
879
22
    timeval tv;
880
22
    gettimeofday(&tv, nullptr);
881
22
    struct tm tm;
882
22
    time_t cur_sec = tv.tv_sec;
883
22
    localtime_r(&cur_sec, &tm);
884
22
    char buf[64];
885
22
    strftime(buf, 64, "%Y%m%d%H%M%S", &tm);
886
22
    std::stringstream ss;
887
22
    ss << prefix << "/" << req->param(HTTP_TABLE_KEY) << "." << buf << "." << tv.tv_usec;
888
22
    *file_path = ss.str();
889
22
    return Status::OK();
890
22
}
891
892
void StreamLoadAction::_save_stream_load_record(std::shared_ptr<StreamLoadContext> ctx,
893
5.11k
                                                const std::string& str) {
894
5.11k
    std::shared_ptr<StreamLoadRecorder> stream_load_recorder =
895
5.11k
            ExecEnv::GetInstance()->storage_engine().get_stream_load_recorder();
896
897
5.12k
    if (stream_load_recorder != nullptr) {
898
5.12k
        std::string key =
899
5.12k
                std::to_string(ctx->start_millis + ctx->load_cost_millis) + "_" + ctx->label;
900
5.12k
        auto st = stream_load_recorder->put(key, str);
901
5.12k
        if (st.ok()) {
902
5.12k
            LOG(INFO) << "put stream_load_record rocksdb successfully. label: " << ctx->label
903
5.12k
                      << ", key: " << key;
904
5.12k
        }
905
18.4E
    } else {
906
18.4E
        LOG(WARNING) << "put stream_load_record rocksdb failed. stream_load_recorder is null.";
907
18.4E
    }
908
5.11k
}
909
910
Status StreamLoadAction::_check_wal_space(const std::string& group_commit_mode,
911
4.79k
                                          int64_t content_length) {
912
4.79k
    if (iequal(group_commit_mode, ASYNC_MODE) &&
913
4.79k
        !load_size_smaller_than_wal_limit(content_length)) {
914
0
        std::stringstream ss;
915
0
        ss << "There is no space for group commit stream load async WAL. This stream load "
916
0
              "size is "
917
0
           << content_length
918
0
           << ". WAL dir info: " << ExecEnv::GetInstance()->wal_mgr()->get_wal_dirs_info_string();
919
0
        LOG(WARNING) << ss.str();
920
0
        return Status::Error<EXCEEDED_LIMIT>(ss.str());
921
0
    }
922
4.79k
    return Status::OK();
923
4.79k
}
924
925
Status StreamLoadAction::_can_group_commit(HttpRequest* req, std::shared_ptr<StreamLoadContext> ctx,
926
                                           std::string& group_commit_header,
927
5.37k
                                           bool& can_group_commit) {
928
5.37k
    int64_t content_length = 0;
929
5.37k
    const auto& content_length_str = req->header(HttpHeaders::CONTENT_LENGTH);
930
5.37k
    if (!content_length_str.empty()) {
931
5.08k
        try {
932
5.08k
            content_length = std::stoll(content_length_str);
933
5.08k
        } catch (const std::exception& e) {
934
0
            return Status::InvalidArgument("invalid HTTP header CONTENT_LENGTH={}: {}",
935
0
                                           content_length_str, e.what());
936
0
        }
937
5.08k
    }
938
5.37k
    if (content_length < 0) {
939
0
        std::stringstream ss;
940
0
        ss << "This stream load content length <0 (" << content_length
941
0
           << "), please check your content length.";
942
0
        LOG(WARNING) << ss.str();
943
0
        return Status::InvalidArgument(ss.str());
944
0
    }
945
5.37k
    auto is_chunk = !req->header(HttpHeaders::TRANSFER_ENCODING).empty() &&
946
5.37k
                    req->header(HttpHeaders::TRANSFER_ENCODING).find(CHUNK) != std::string::npos;
947
5.37k
    if (content_length == 0 && !is_chunk) {
948
        // off_mode and empty
949
7
        can_group_commit = false;
950
7
        return Status::OK();
951
7
    }
952
5.36k
    if (is_chunk) {
953
285
        ctx->label = "";
954
285
    }
955
956
5.36k
    auto partial_columns = !req->header(HTTP_PARTIAL_COLUMNS).empty() &&
957
5.36k
                           iequal(req->header(HTTP_PARTIAL_COLUMNS), "true");
958
5.36k
    auto temp_partitions = !req->header(HTTP_TEMP_PARTITIONS).empty();
959
5.36k
    auto partitions = !req->header(HTTP_PARTITIONS).empty();
960
5.36k
    auto update_mode =
961
5.36k
            !req->header(HTTP_UNIQUE_KEY_UPDATE_MODE).empty() &&
962
5.36k
            (iequal(req->header(HTTP_UNIQUE_KEY_UPDATE_MODE), "UPDATE_FIXED_COLUMNS") ||
963
170
             iequal(req->header(HTTP_UNIQUE_KEY_UPDATE_MODE), "UPDATE_FLEXIBLE_COLUMNS"));
964
5.36k
    if (!partial_columns && !partitions && !temp_partitions && !ctx->two_phase_commit &&
965
5.36k
        !update_mode) {
966
4.79k
        if (!config::wait_internal_group_commit_finish && !group_commit_header.empty() &&
967
4.79k
            !ctx->label.empty()) {
968
1
            return Status::InvalidArgument("label and group_commit can't be set at the same time");
969
1
        }
970
4.79k
        RETURN_IF_ERROR(_check_wal_space(group_commit_header, content_length));
971
4.79k
        can_group_commit = true;
972
4.79k
    }
973
5.36k
    return Status::OK();
974
5.36k
}
975
976
Status StreamLoadAction::_handle_group_commit(HttpRequest* req,
977
5.37k
                                              std::shared_ptr<StreamLoadContext> ctx) {
978
5.37k
    std::string group_commit_header = req->header(HTTP_GROUP_COMMIT);
979
5.37k
    if (!group_commit_header.empty() && !iequal(group_commit_header, SYNC_MODE) &&
980
5.37k
        !iequal(group_commit_header, ASYNC_MODE) && !iequal(group_commit_header, OFF_MODE)) {
981
0
        return Status::InvalidArgument(
982
0
                "group_commit can only be [async_mode, sync_mode, off_mode]");
983
0
    }
984
5.37k
    if (config::wait_internal_group_commit_finish) {
985
0
        group_commit_header = SYNC_MODE;
986
0
    }
987
988
    // if group_commit_header is off_mode, we will not use group commit
989
5.37k
    if (iequal(group_commit_header, OFF_MODE)) {
990
1
        ctx->group_commit_mode = OFF_MODE;
991
1
        ctx->group_commit = false;
992
1
        return Status::OK();
993
1
    }
994
5.37k
    bool can_group_commit = false;
995
5.37k
    RETURN_IF_ERROR(_can_group_commit(req, ctx, group_commit_header, can_group_commit));
996
5.36k
    if (!can_group_commit) {
997
577
        ctx->group_commit_mode = OFF_MODE;
998
577
        ctx->group_commit = false;
999
4.79k
    } else {
1000
4.79k
        if (!group_commit_header.empty()) {
1001
424
            ctx->group_commit_mode = group_commit_header;
1002
424
            ctx->group_commit = true;
1003
4.36k
        } else {
1004
            // use table property to decide group commit or not
1005
4.36k
            ctx->group_commit_mode = "";
1006
4.36k
            ctx->group_commit = false;
1007
4.36k
        }
1008
4.79k
    }
1009
5.36k
    return Status::OK();
1010
5.37k
}
1011
1012
} // namespace doris