libpqxx
The C++ client library for PostgreSQL
Loading...
Searching...
No Matches
stream_to.hxx
1/* Definition of the pqxx::stream_to class.
2 *
3 * pqxx::stream_to enables optimized batch updates to a database table.
4 *
5 * DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/stream_to.hxx instead.
6 *
7 * Copyright (c) 2000-2024, Jeroen T. Vermeulen.
8 *
9 * See COPYING for copyright license. If you did not receive a file called
10 * COPYING with this source code, please notify the distributor of this
11 * mistake, or contact the author.
12 */
13#ifndef PQXX_H_STREAM_TO
14#define PQXX_H_STREAM_TO
15
16#if !defined(PQXX_HEADER_PRE)
17# error "Include libpqxx headers as <pqxx/header>, not <pqxx/header.hxx>."
18#endif
19
20#include "pqxx/separated_list.hxx"
21#include "pqxx/transaction_base.hxx"
22
23
24namespace pqxx
25{
27
80class PQXX_LIBEXPORT stream_to : transaction_focus
81{
82public:
84
104 static stream_to raw_table(
105 transaction_base &tx, std::string_view path, std::string_view columns = "")
106 {
107 return {tx, path, columns};
108 }
109
111
120 static stream_to table(
121 transaction_base &tx, table_path path,
122 std::initializer_list<std::string_view> columns = {})
123 {
124 auto const &cx{tx.conn()};
125 return raw_table(tx, cx.quote_table(path), cx.quote_columns(columns));
126 }
127
128#if defined(PQXX_HAVE_CONCEPTS)
130
137 template<PQXX_CHAR_STRINGS_ARG COLUMNS>
138 static stream_to
139 table(transaction_base &tx, table_path path, COLUMNS const &columns)
140 {
141 auto const &cx{tx.conn()};
142 return stream_to::raw_table(
143 tx, cx.quote_table(path), tx.conn().quote_columns(columns));
144 }
145
147
154 template<PQXX_CHAR_STRINGS_ARG COLUMNS>
155 static stream_to
156 table(transaction_base &tx, std::string_view path, COLUMNS const &columns)
157 {
158 return stream_to::raw_table(tx, path, tx.conn().quote_columns(columns));
159 }
160#endif // PQXX_HAVE_CONCEPTS
161
162 explicit stream_to(stream_to &&other) :
163 // (This first step only moves the transaction_focus base-class
164 // object.)
165 transaction_focus{std::move(other)},
166 m_finished{other.m_finished},
167 m_buffer{std::move(other.m_buffer)},
168 m_field_buf{std::move(other.m_field_buf)},
169 m_finder{other.m_finder}
170 {
171 other.m_finished = true;
172 }
173 ~stream_to() noexcept;
174
176 [[nodiscard]] constexpr operator bool() const noexcept
177 {
178 return not m_finished;
179 }
181 [[nodiscard]] constexpr bool operator!() const noexcept
182 {
183 return m_finished;
184 }
185
187
193 void complete();
194
196
205 template<typename Row> stream_to &operator<<(Row const &row)
206 {
207 write_row(row);
208 return *this;
209 }
210
212
216 stream_to &operator<<(stream_from &);
217
219
225 template<typename Row> void write_row(Row const &row)
226 {
227 fill_buffer(row);
228 write_buffer();
229 }
230
232
235 template<typename... Ts> void write_values(Ts const &...fields)
236 {
237 fill_buffer(fields...);
238 write_buffer();
239 }
240
242
251 [[deprecated("Use table() or raw_table() factory.")]] stream_to(
252 transaction_base &tx, std::string_view table_name) :
253 stream_to{tx, table_name, ""sv}
254 {}
255
257
259 template<typename Columns>
260 [[deprecated("Use table() or raw_table() factory.")]] stream_to(
261 transaction_base &, std::string_view table_name, Columns const &columns);
262
263private:
265 stream_to(
266 transaction_base &tx, std::string_view path, std::string_view columns);
267
268 bool m_finished = false;
269
271 std::string m_buffer;
272
274 std::string m_field_buf;
275
277 internal::char_finder_func *m_finder;
278
280 void write_raw_line(std::string_view);
281
283
285 void write_buffer();
286
288 static constexpr std::string_view null_field{"\\N\t"};
289
291 template<typename T>
292 static std::enable_if_t<nullness<T>::always_null, std::size_t>
293 estimate_buffer(T const &)
294 {
295 return std::size(null_field);
296 }
297
299
302 template<typename T>
303 static std::enable_if_t<not nullness<T>::always_null, std::size_t>
304 estimate_buffer(T const &field)
305 {
306 return is_null(field) ? std::size(null_field) : size_buffer(field);
307 }
308
310 void escape_field_to_buffer(std::string_view data);
311
313
319 template<typename Field>
320 std::enable_if_t<not nullness<Field>::always_null>
321 append_to_buffer(Field const &f)
322 {
323 // We append each field, terminated by a tab. That will leave us with
324 // one tab too many, assuming we write any fields at all; we remove that
325 // at the end.
326 if (is_null(f))
327 {
328 // Easy. Append null and tab in one go.
329 m_buffer.append(null_field);
330 }
331 else
332 {
333 // Convert f into m_buffer.
334
335 using traits = string_traits<Field>;
336 auto const budget{estimate_buffer(f)};
337 auto const offset{std::size(m_buffer)};
338
339 if constexpr (std::is_arithmetic_v<Field>)
340 {
341 // Specially optimised for "safe" types, which never need any
342 // escaping. Convert straight into m_buffer.
343
344 // The budget we get from size_buffer() includes room for the trailing
345 // zero, which we must remove. But we're also inserting tabs between
346 // fields, so we re-purpose the extra byte for that.
347 auto const total{offset + budget};
348 m_buffer.resize(total);
349 auto const data{m_buffer.data()};
350 char *const end{traits::into_buf(data + offset, data + total, f)};
351 *(end - 1) = '\t';
352 // Shrink to fit. Keep the tab though.
353 m_buffer.resize(static_cast<std::size_t>(end - data));
354 }
355 else if constexpr (
356 std::is_same_v<Field, std::string> or
357 std::is_same_v<Field, std::string_view> or
358 std::is_same_v<Field, zview>)
359 {
360 // This string may need escaping.
361 m_field_buf.resize(budget);
362 escape_field_to_buffer(f);
363 }
364 else if constexpr (
365 std::is_same_v<Field, std::optional<std::string>> or
366 std::is_same_v<Field, std::optional<std::string_view>> or
367 std::is_same_v<Field, std::optional<zview>>)
368 {
369 // Optional string. It's not null (we checked for that above), so...
370 // Treat like a string.
371 m_field_buf.resize(budget);
372 escape_field_to_buffer(f.value());
373 }
374 // TODO: Support deleter template argument on unique_ptr.
375 else if constexpr (
376 std::is_same_v<Field, std::unique_ptr<std::string>> or
377 std::is_same_v<Field, std::unique_ptr<std::string_view>> or
378 std::is_same_v<Field, std::unique_ptr<zview>> or
379 std::is_same_v<Field, std::shared_ptr<std::string>> or
380 std::is_same_v<Field, std::shared_ptr<std::string_view>> or
381 std::is_same_v<Field, std::shared_ptr<zview>>)
382 {
383 // TODO: Can we generalise this elegantly without Concepts?
384 // Effectively also an optional string. It's not null (we checked
385 // for that above).
386 m_field_buf.resize(budget);
387 escape_field_to_buffer(*f);
388 }
389 else
390 {
391 // This field needs to be converted to a string, and after that,
392 // escaped as well.
393 m_field_buf.resize(budget);
394 auto const data{m_field_buf.data()};
395 escape_field_to_buffer(
396 traits::to_buf(data, data + std::size(m_field_buf), f));
397 }
398 }
399 }
400
402
408 template<typename Field>
409 std::enable_if_t<nullness<Field>::always_null>
410 append_to_buffer(Field const &)
411 {
412 m_buffer.append(null_field);
413 }
414
416 template<typename Container>
417 std::enable_if_t<not std::is_same_v<typename Container::value_type, char>>
418 fill_buffer(Container const &c)
419 {
420 // To avoid unnecessary allocations and deallocations, we run through c
421 // twice: once to determine how much buffer space we may need, and once to
422 // actually write it into the buffer.
423 std::size_t budget{0};
424 for (auto const &f : c) budget += estimate_buffer(f);
425 m_buffer.reserve(budget);
426 for (auto const &f : c) append_to_buffer(f);
427 }
428
430 template<typename Tuple, std::size_t... indexes>
431 static std::size_t
432 budget_tuple(Tuple const &t, std::index_sequence<indexes...>)
433 {
434 return (estimate_buffer(std::get<indexes>(t)) + ...);
435 }
436
438 template<typename Tuple, std::size_t... indexes>
439 void append_tuple(Tuple const &t, std::index_sequence<indexes...>)
440 {
441 (append_to_buffer(std::get<indexes>(t)), ...);
442 }
443
445 template<typename... Elts> void fill_buffer(std::tuple<Elts...> const &t)
446 {
447 using indexes = std::make_index_sequence<sizeof...(Elts)>;
448
449 m_buffer.reserve(budget_tuple(t, indexes{}));
450 append_tuple(t, indexes{});
451 }
452
454 template<typename... Ts> void fill_buffer(const Ts &...fields)
455 {
456 (..., append_to_buffer(fields));
457 }
458
459 constexpr static std::string_view s_classname{"stream_to"};
460};
461
462
463template<typename Columns>
464inline stream_to::stream_to(
465 transaction_base &tx, std::string_view table_name, Columns const &columns) :
466 stream_to{tx, table_name, std::begin(columns), std::end(columns)}
467{}
468} // namespace pqxx
469#endif
Base class for things that monopolise a transaction's attention.
Definition transaction_focus.hxx:29
The home of all libpqxx classes, functions, templates, etc.
Definition array.cxx:27
std::basic_ostream< CHAR > & operator<<(std::basic_ostream< CHAR > &s, field const &value)
Write a result field to any type of stream.
Definition field.hxx:537
std::size_t size_buffer(TYPE const &...value) noexcept
Estimate how much buffer space is needed to represent values as a string.
Definition strconv.hxx:526
constexpr bool is_null(TYPE const &value) noexcept
Is value null?
Definition strconv.hxx:515