MySQL 26.7.0
Source Code Documentation
bulk_load_service.h
Go to the documentation of this file.
1/* Copyright (c) 2022, 2026, Oracle and/or its affiliates.
2
3 This program is free software; you can redistribute it and/or modify
4 it under the terms of the GNU General Public License, version 2.0,
5 as published by the Free Software Foundation.
6
7 This program is designed to work with certain software (including
8 but not limited to OpenSSL) that is licensed under separate terms,
9 as designated in a particular file or component or in included license
10 documentation. The authors of MySQL hereby grant you an additional
11 permission to link the program and your derivative works with the
12 separately licensed software that they have either included with
13 the program or referenced in the documentation.
14
15 This program is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 GNU General Public License, version 2.0, for more details.
19
20 You should have received a copy of the GNU General Public License
21 along with this program; if not, write to the Free Software
22 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
23
24#pragma once
25
26/**
27 @file
28 This service provides interface for loading data in bulk from CSV files.
29
30*/
31
32#include "my_rapidjson_size_t.h"
33
35#include <rapidjson/document.h>
36#include <rapidjson/error/en.h>
37#include <rapidjson/rapidjson.h>
38#include <rapidjson/stringbuffer.h>
39#include <rapidjson/writer.h>
40
41#include <algorithm>
42#include <cctype>
43#include <cstdlib>
44#include <optional>
45#include <sstream>
46#include <string>
47#include <unordered_map>
48#include <unordered_set>
49#include <vector>
50#include "m_string.h"
51#include "my_thread_local.h"
52
53/* Forward declaration for opaque types. */
54class THD;
55struct TABLE;
56struct CHARSET_INFO;
57
58using Bulk_loader = void;
59
60/** Bulk loader source. */
61enum class Bulk_source {
62 /** Local file system. */
63 LOCAL,
64 /** OCI object store. */
65 OCI,
66 /** Amazon S3. */
67 S3
68};
69
70inline std::string trim_left(const std::string &s) {
71 auto pos = s.find_first_not_of(" \n\r\t");
72 return s.substr(pos);
73}
74
78 const std::string &input_string, const size_t &n_files)
80 m_input_string(trim_left(input_string)),
82
83 std::string m_file_prefix;
84 std::optional<std::string> m_file_suffix;
86 size_t m_start_index{1};
87 bool m_is_dryrun{false};
88 std::string m_current_partition{};
89 std::unordered_map<std::string, std::vector<int>> m_partitions{};
90
91 bool parse(std::string &error);
92
93 std::ostream &print(std::ostream &out) const;
94
95 /** Check if the COUNT clause has been explicitly specified.
96 @return true if COUNT is specified explicitly, false otherwise. */
97 bool is_count_specified() const { return m_n_files > 0; }
98
99 private:
101 std::string m_input_string;
102
103 /* This value can be 0, only if COUNT clause is not specified. If COUNT
104 clause is specified, this value will be greater than 0. */
105 size_t m_n_files{0};
106};
107
108inline std::ostream &Bulk_load_file_info::print(std::ostream &out) const {
109 std::string suffix = m_file_suffix.has_value() ? m_file_suffix.value() : "";
110 out << "[Bulk_load_file_info: m_file_prefix=" << m_file_prefix << ", "
111 << "m_file_suffix=" << suffix << ", "
112 << "m_appendtolastprefix=" << m_appendtolastprefix << ", "
113 << "m_start_index=" << m_start_index << ", "
114 << "m_is_dryrun=" << m_is_dryrun << "]";
115 return out;
116}
117
118inline std::ostream &operator<<(std::ostream &out,
119 const Bulk_load_file_info &obj) {
120 return obj.print(out);
121}
122
123/** Check whether the specified argument is a valid JSON object. Used to check
124whether the user specified JSON or a regular filename as the LOAD location
125argument.
126@param[in] file_name_arg filename argument provided by the user.
127@return true if the arg is a JSON object. */
128inline static bool is_json_object(const std::string &file_name_arg) {
129 rapidjson::Document doc;
130 doc.Parse(file_name_arg.c_str());
131 return !doc.HasParseError() && doc.IsObject();
132}
133
134/** Validates whether the json argument matches the expected schema for bulk
135load, if it matches it fills out the Bulk_load_input structure, sets error
136and returns false otherwise.
137@param[out] error contains the appropriate error message.
138@param[out] info parsed structure of file information (containing prefix and
139optional suffix)
140@param[in] doc rapidjson document
141@return false if configuration contains unknown or unsupported values. */
142inline static bool parse_input_arg(std::string &error,
144 const rapidjson::Document &doc) {
145 constexpr char PREFIX_KEY[] = "url-prefix";
146 constexpr char SUFFIX_KEY[] = "url-suffix";
147 constexpr char APPENDTOLASTPREFIX_KEY[] = "url-prefix-last-append";
148 constexpr char SEQUENCE_START_KEY[] = "url-sequence-start";
149 constexpr char DRYRUN_KEY[] = "is-dryrun";
150 static const std::unordered_set<std::string> all_keys = {
151 PREFIX_KEY, SUFFIX_KEY, APPENDTOLASTPREFIX_KEY, SEQUENCE_START_KEY,
152 DRYRUN_KEY};
153
154 if (!doc.IsObject()) {
155 error = "Invalid JSON object used for filename argument!";
156 return false;
157 }
158
159 for (const auto &child : doc.GetObject()) {
160 std::string key = child.name.GetString();
161 if (all_keys.find(key) == all_keys.end()) {
162 std::stringstream ss;
163 ss << "Unsupported JSON key: " << key;
164 error = ss.str();
165 return false;
166 }
167 }
168
169 if (!doc.HasMember(PREFIX_KEY)) {
170 error = "Missing url-prefix in JSON filename argument!";
171 return false;
172 }
173
174 if (!doc[PREFIX_KEY].IsString()) {
175 std::stringstream ss;
176 ss << "The value of key " << PREFIX_KEY << " must be a string";
177 error = ss.str();
178 return false;
179 }
180
181 info.m_file_prefix = doc[PREFIX_KEY].GetString();
182
183 if (doc.HasMember(SUFFIX_KEY)) {
184 if (!info.is_count_specified()) {
186 sout << "Cannot specify " << SUFFIX_KEY << " without COUNT clause";
187 error = sout.str();
188 return false;
189 }
190
191 if (!doc[SUFFIX_KEY].IsString()) {
192 std::stringstream ss;
193 ss << "The value of key " << SUFFIX_KEY << " must be a string";
194 error = ss.str();
195 return false;
196 }
197 info.m_file_suffix = doc[SUFFIX_KEY].GetString();
198 }
199
200 if (doc.HasMember(APPENDTOLASTPREFIX_KEY)) {
201 if (!info.is_count_specified()) {
203 sout << "Cannot specify " << APPENDTOLASTPREFIX_KEY
204 << " without COUNT clause";
205 error = sout.str();
206 return false;
207 }
208 if (!doc[APPENDTOLASTPREFIX_KEY].IsString()) {
209 std::stringstream ss;
210 ss << "The value of key " << APPENDTOLASTPREFIX_KEY
211 << " must be a string";
212 error = ss.str();
213 return false;
214 }
215 info.m_appendtolastprefix = doc[APPENDTOLASTPREFIX_KEY].GetString();
216 }
217
218 if (doc.HasMember(SEQUENCE_START_KEY)) {
219 if (!info.is_count_specified()) {
221 sout << "Cannot specify " << SEQUENCE_START_KEY
222 << " without COUNT clause";
223 error = sout.str();
224 return false;
225 }
226 if (doc[SEQUENCE_START_KEY].IsInt64()) {
227 /* Check for -ve numbers and report error */
228 const int64_t val = doc[SEQUENCE_START_KEY].GetInt64();
229 if (val < 0) {
231 sout << "The value of key " << SEQUENCE_START_KEY
232 << " cannot be negative: (" << val << ")";
233 error = sout.str();
234 return false;
235 }
236 }
237 if (doc[SEQUENCE_START_KEY].IsUint64()) {
238 info.m_start_index = doc[SEQUENCE_START_KEY].GetUint64();
239 } else if (doc[SEQUENCE_START_KEY].IsString()) {
240 const std::string val = doc[SEQUENCE_START_KEY].GetString();
241 if (val.empty()) {
243 sout << "The value of key " << SEQUENCE_START_KEY << " cannot be empty";
244 error = sout.str();
245 return false;
246 } else if ((val.length() == 7) &&
247 (native_strncasecmp(val.c_str(), "default", 7) == 0)) {
248 info.m_start_index = 1;
249 } else if (std::all_of(val.begin(), val.end(),
250 [](unsigned char c) { return std::isdigit(c); })) {
251 info.m_start_index = std::strtoull(val.c_str(), nullptr, 10);
252 } else {
254 sout << "The value of key " << SEQUENCE_START_KEY << " is invalid ("
255 << val << ")";
256 error = sout.str();
257 return false;
258 }
259 } else {
261 sout << "Invalid value for key " << SEQUENCE_START_KEY;
262 error = sout.str();
263 return false;
264 }
265 }
266
267 if (doc.HasMember(DRYRUN_KEY)) {
268 if (doc[DRYRUN_KEY].IsBool()) {
269 info.m_is_dryrun = doc[DRYRUN_KEY].GetBool();
270 } else if (doc[DRYRUN_KEY].IsString()) {
271 const std::string val = doc[DRYRUN_KEY].GetString();
272 if (val == "1" || (native_strncasecmp(val.c_str(), "on", 2) == 0) ||
273 (native_strncasecmp(val.c_str(), "true", 4) == 0)) {
274 info.m_is_dryrun = true;
275 } else if (val == "0" ||
276 (native_strncasecmp(val.c_str(), "off", 3) == 0) ||
277 (native_strncasecmp(val.c_str(), "false", 5)) == 0) {
278 info.m_is_dryrun = false;
279 } else {
281 sout << "Unsupported " << DRYRUN_KEY << " value: " << val;
282 error = sout.str();
283 return false;
284 }
285 } else if (doc[DRYRUN_KEY].IsUint64()) {
286 const uint64_t val = doc[DRYRUN_KEY].GetUint64();
287 if (val == 0) {
288 info.m_is_dryrun = false;
289 } else if (val == 1) {
290 info.m_is_dryrun = true;
291 } else {
293 sout << "Unsupported " << DRYRUN_KEY << " value: " << val;
294 error = sout.str();
295 return false;
296 }
297 } else {
298 std::stringstream ss;
299 ss << "Invalid value for key " << DRYRUN_KEY;
300 error = ss.str();
301 return false;
302 }
303 } else {
304 info.m_is_dryrun = false;
305 }
306
307 error = "";
308 return true;
309}
310
311inline bool Bulk_load_file_info::parse(std::string &error) {
312 rapidjson::Document doc;
313 rapidjson::ParseResult ok = doc.Parse(m_input_string.c_str());
314 std::string parse_error;
315
316 if (!ok) {
317 parse_error = rapidjson::GetParseError_En(ok.Code());
318 }
319
320 if (!doc.HasParseError()) {
321 if (!parse_input_arg(error, *this, doc)) {
322 return false;
323 }
324 } else {
325 if (m_source == Bulk_source::OCI) {
326 auto pos = m_input_string.find(":");
327 std::string protocol = m_input_string.substr(0, pos);
328 std::for_each(protocol.begin(), protocol.end(),
329 [](unsigned char c) { return std::tolower(c); });
330 if (protocol == "http" || protocol == "https") {
331 /* Protocol is supported. */
332 } else {
334 if (protocol.starts_with('{')) {
335 sout << "Could be malformed JSON (" << parse_error << ") or ";
336 }
337 sout << "Unsupported protocol in URL";
338 error = sout.str();
339 return false;
340 }
341 } else if (m_source == Bulk_source::LOCAL) {
342 /* Nothing yet. */
343 }
344
345 /* In case json parsing failed, the file_name_arg only contains the file
346 * prefix. */
348 m_file_suffix = std::nullopt;
349 }
350 return true;
351}
352
353/** Bulk data compression algorithm. */
355
356/** Bulk loader string attributes. */
357enum class Bulk_string {
358 /** Schema name */
360 /* SQL table name */
362 /* Duplicate table name */
364 /* File prefix URL */
366 /* File suffix */
368 /** Column terminator */
370 /** Row terminator */
371 ROW_TERM,
372 /** String to append to last file prefix. */
374};
375
376/** Bulk loader boolean attributes. */
377enum class Bulk_condition {
378 /** The algorithm used is different based on whether the data is in sorted
379 primary key order. This option tells whether to expect sorted input. */
381 /** If enclosing is optional. */
383 /** If true, the current execution is only a dry run. No need to load data
384 into the table. */
385 DRYRUN,
386 /** If true, we are loading data into a non-empty table. */
388};
389
390/** Bulk loader size attributes. */
391enum class Bulk_size {
392 /** Number of input files. */
394 /** Number of rows to skip. */
396 /** Number of columns in the table. */
398 /** Number of concurrent loaders to use, */
400 /** Total memory size to use for LOAD in bytes. */
401 MEMORY,
402 /** Index of the first file. */
404};
405
406/** Bulk loader single byte attributes. */
407enum class Bulk_char {
408 /** Escape character. */
410 /** Column enclosing character. */
412};
413
414/** Bulk load driver service. */
416
417/**
418 Create bulk loader.
419 @param[in] thd mysql THD
420 @param[in] sql_table mysql TABLE object
421 @param[in] duplicate_table mysql TABLE object
422 @param[in] src bulk loader source
423 @param[in] charset source data character set
424 @return bulk loader object, opaque type.
425*/
426DECLARE_METHOD(Bulk_loader *, create_bulk_loader,
427 (THD * thd, my_thread_id connection_id, const TABLE *sql_table,
428 const TABLE *duplicate_table, Bulk_source src,
430/**
431 Set string attribute for loading data.
432 @param[in,out] loader bulk loader
433 @param[in] type attribute type
434 @param[in] value attribute value
435*/
436DECLARE_METHOD(void, set_string,
438/**
439 Set single byte character attribute for loading data.
440 @param[in,out] loader bulk loader
441 @param[in] type attribute type
442 @param[in] value attribute value
443*/
444DECLARE_METHOD(void, set_char,
445 (Bulk_loader * loader, Bulk_char type, unsigned char value));
446/**
447 Set size attribute for loading data.
448 @param[in,out] loader bulk loader
449 @param[in] type attribute type
450 @param[in] value attribute value
451*/
452DECLARE_METHOD(void, set_size,
454/**
455 Set boolean condition attribute for loading data.
456 @param[in,out] loader bulk loader
457 @param[in] type attribute type
458 @param[in] value attribute value
459*/
460DECLARE_METHOD(void, set_condition,
462
463/**
464 Set boolean condition attribute for loading data.
465 @param[in,out] loader bulk loader
466 @param[in] algorithm the compression algorithm used
467*/
468DECLARE_METHOD(void, set_compression_algorithm,
470
471/**
472 Load data from CSV files.
473 @param[in,out] loader bulk loader
474 @return true if successful.
475*/
476DECLARE_METHOD(bool, load, (Bulk_loader * loader, size_t &affected_rows));
477
478/**
479 Drop bulk loader.
480 @param[in,out] thd mysql THD
481 @param[in,out] loader loader object to drop
482*/
483DECLARE_METHOD(void, drop_bulk_loader, (THD * thd, Bulk_loader *loader));
484
485END_SERVICE_DEFINITION(bulk_load_driver)
Bulk_char
Bulk loader single byte attributes.
Definition: bulk_load_service.h:407
@ ENCLOSE_CHAR
Column enclosing character.
@ ESCAPE_CHAR
Escape character.
static bool parse_input_arg(std::string &error, Bulk_load_file_info &info, const rapidjson::Document &doc)
Validates whether the json argument matches the expected schema for bulk load, if it matches it fills...
Definition: bulk_load_service.h:142
Bulk_condition
Bulk loader boolean attributes.
Definition: bulk_load_service.h:377
@ OPTIONAL_ENCLOSE
If enclosing is optional.
@ ORDERED_DATA
The algorithm used is different based on whether the data is in sorted primary key order.
@ NON_EMPTY_TABLE
If true, we are loading data into a non-empty table.
@ DRYRUN
If true, the current execution is only a dry run.
Bulk_source
Bulk loader source.
Definition: bulk_load_service.h:61
@ LOCAL
Local file system.
@ OCI
OCI object store.
@ S3
Amazon S3.
Bulk_compression_algorithm
Bulk data compression algorithm.
Definition: bulk_load_service.h:354
std::ostream & operator<<(std::ostream &out, const Bulk_load_file_info &obj)
Definition: bulk_load_service.h:118
static bool is_json_object(const std::string &file_name_arg)
Check whether the specified argument is a valid JSON object.
Definition: bulk_load_service.h:128
std::string trim_left(const std::string &s)
Definition: bulk_load_service.h:70
void Bulk_loader
Definition: bulk_load_service.h:58
Bulk_string
Bulk loader string attributes.
Definition: bulk_load_service.h:357
@ COLUMN_TERM
Column terminator.
@ DUPLICATE_TABLE_NAME
@ ROW_TERM
Row terminator.
@ APPENDTOLASTPREFIX
String to append to last file prefix.
@ SCHEMA_NAME
Schema name.
Bulk_size
Bulk loader size attributes.
Definition: bulk_load_service.h:391
@ COUNT_COLUMNS
Number of columns in the table.
@ MEMORY
Total memory size to use for LOAD in bytes.
@ START_INDEX
Index of the first file.
@ CONCURRENCY
Number of concurrent loaders to use,.
@ COUNT_ROW_SKIP
Number of rows to skip.
@ COUNT_FILES
Number of input files.
static Mysys_charset_loader * loader
Definition: charset.cc:197
For each client connection we create a separate thread with THD serving as a thread/connection descri...
Definition: sql_lexer_thd.h:36
static int native_strncasecmp(const char *s1, const char *s2, size_t n)
Definition: m_string.h:216
Define rapidjson::SizeType to be std::uint64_t.
uint32 my_thread_id
Definition: my_thread_local.h:34
void error(const char *format,...)
void for_each(const Shards< COUNT > &shards, Function &&f) noexcept
Iterate over the shards.
Definition: ut0counter.h:323
const std::string charset("charset")
ulong n_files
Number of files to use for the double write buffer.
Definition: buf0dblwr.cc:83
bool load(THD *, const dd::String_type &fname, dd::String_type *buf)
Read an sdi file from disk and store in a buffer.
Definition: sdi_file.cc:308
ValueType value(const std::optional< ValueType > &v)
Definition: gtid.h:83
std::basic_ostringstream< char, std::char_traits< char >, ut::allocator< char > > ostringstream
Specialization of basic_ostringstream which uses ut::allocator.
Definition: ut0new.h:2720
required string key
Definition: replication_asynchronous_connection_failover.proto:60
repeated Source source
Definition: replication_asynchronous_connection_failover.proto:42
required string type
Definition: replication_group_member_actions.proto:34
#define DECLARE_METHOD(retval, name, args)
Declares a method as a part of the Service definition.
Definition: service.h:103
#define END_SERVICE_DEFINITION(name)
A macro to end the last Service definition started with the BEGIN_SERVICE_DEFINITION macro.
Definition: service.h:91
#define BEGIN_SERVICE_DEFINITION(name)
Declares a new Service.
Definition: service.h:86
Definition: bulk_load_service.h:75
std::string m_input_string
Definition: bulk_load_service.h:101
size_t m_n_files
Definition: bulk_load_service.h:105
size_t m_start_index
Definition: bulk_load_service.h:86
std::string m_appendtolastprefix
Definition: bulk_load_service.h:85
Bulk_load_file_info()=default
std::ostream & print(std::ostream &out) const
Definition: bulk_load_service.h:108
std::string m_current_partition
Definition: bulk_load_service.h:88
std::string m_file_prefix
Definition: bulk_load_service.h:83
std::unordered_map< std::string, std::vector< int > > m_partitions
Definition: bulk_load_service.h:89
bool is_count_specified() const
Check if the COUNT clause has been explicitly specified.
Definition: bulk_load_service.h:97
bool m_is_dryrun
Definition: bulk_load_service.h:87
Bulk_source m_source
Definition: bulk_load_service.h:100
Bulk_load_file_info(const Bulk_source &source, const std::string &input_string, const size_t &n_files)
Definition: bulk_load_service.h:77
bool parse(std::string &error)
Definition: bulk_load_service.h:311
std::optional< std::string > m_file_suffix
Definition: bulk_load_service.h:84
Definition: m_ctype.h:421
Definition: table.h:1456