libpqxx 7.8.0
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-2023, 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
105 transaction_base &tx, std::string_view path, std::string_view columns = "")
106 {
107 return {tx, path, columns};
108 }
109
111
122 std::initializer_list<std::string_view> columns = {})
123 {
124 auto const &conn{tx.conn()};
125 return raw_table(tx, conn.quote_table(path), conn.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 &conn{tx.conn()};
142 return stream_to::raw_table(
143 tx, conn.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
163
172 [[deprecated("Use table() or raw_table() factory.")]] stream_to(
173 transaction_base &tx, std::string_view table_name) :
174 stream_to{tx, table_name, ""sv}
175 {}
176
178
180 template<typename Columns>
181 [[deprecated("Use table() or raw_table() factory.")]] stream_to(
182 transaction_base &, std::string_view table_name, Columns const &columns);
183
185
187 template<typename Iter>
188 [[deprecated("Use table() or raw_table() factory.")]] stream_to(
189 transaction_base &, std::string_view table_name, Iter columns_begin,
190 Iter columns_end);
191
192 explicit stream_to(stream_to &&other) :
193 // (This first step only moves the transaction_focus base-class object.)
194 transaction_focus{std::move(other)},
195 m_finished{other.m_finished},
196 m_buffer{std::move(other.m_buffer)},
197 m_field_buf{std::move(other.m_field_buf)},
198 m_finder{other.m_finder}
199 {
200 other.m_finished = true;
201 }
202 ~stream_to() noexcept;
203
205 [[nodiscard]] constexpr operator bool() const noexcept
206 {
207 return not m_finished;
208 }
210 [[nodiscard]] constexpr bool operator!() const noexcept
211 {
212 return m_finished;
213 }
214
216
222 void complete();
223
225
234 template<typename Row> stream_to &operator<<(Row const &row)
235 {
236 write_row(row);
237 return *this;
238 }
239
241
246
248
254 template<typename Row> void write_row(Row const &row)
255 {
256 fill_buffer(row);
257 write_buffer();
258 }
259
261
264 template<typename... Ts> void write_values(Ts const &...fields)
265 {
266 fill_buffer(fields...);
267 write_buffer();
268 }
269
270private:
272 stream_to(
273 transaction_base &tx, std::string_view path, std::string_view columns);
274
275 bool m_finished = false;
276
278 std::string m_buffer;
279
281 std::string m_field_buf;
282
284 internal::char_finder_func *m_finder;
285
287 void write_raw_line(std::string_view);
288
290
292 void write_buffer();
293
295 static constexpr std::string_view null_field{"\\N\t"};
296
298 template<typename T>
299 static std::enable_if_t<nullness<T>::always_null, std::size_t>
300 estimate_buffer(T const &)
301 {
302 return std::size(null_field);
303 }
304
306
309 template<typename T>
310 static std::enable_if_t<not nullness<T>::always_null, std::size_t>
311 estimate_buffer(T const &field)
312 {
313 return is_null(field) ? std::size(null_field) : size_buffer(field);
314 }
315
317 void escape_field_to_buffer(std::string_view data);
318
320
326 template<typename Field>
327 std::enable_if_t<not nullness<Field>::always_null>
328 append_to_buffer(Field const &f)
329 {
330 // We append each field, terminated by a tab. That will leave us with
331 // one tab too many, assuming we write any fields at all; we remove that
332 // at the end.
333 if (is_null(f))
334 {
335 // Easy. Append null and tab in one go.
336 m_buffer.append(null_field);
337 }
338 else
339 {
340 // Convert f into m_buffer.
341
342 using traits = string_traits<Field>;
343 auto const budget{estimate_buffer(f)};
344 auto const offset{std::size(m_buffer)};
345
346 if constexpr (std::is_arithmetic_v<Field>)
347 {
348 // Specially optimised for "safe" types, which never need any
349 // escaping. Convert straight into m_buffer.
350
351 // The budget we get from size_buffer() includes room for the trailing
352 // zero, which we must remove. But we're also inserting tabs between
353 // fields, so we re-purpose the extra byte for that.
354 auto const total{offset + budget};
355 m_buffer.resize(total);
356 auto const data{m_buffer.data()};
357 char *const end{traits::into_buf(data + offset, data + total, f)};
358 *(end - 1) = '\t';
359 // Shrink to fit. Keep the tab though.
360 m_buffer.resize(static_cast<std::size_t>(end - data));
361 }
362 else if constexpr (
363 std::is_same_v<Field, std::string> or
364 std::is_same_v<Field, std::string_view> or
365 std::is_same_v<Field, zview>)
366 {
367 // This string may need escaping.
368 m_field_buf.resize(budget);
369 escape_field_to_buffer(f);
370 }
371 else if constexpr (
372 std::is_same_v<Field, std::optional<std::string>> or
373 std::is_same_v<Field, std::optional<std::string_view>> or
374 std::is_same_v<Field, std::optional<zview>>)
375 {
376 // Optional string. It's not null (we checked for that above), so...
377 // Treat like a string.
378 m_field_buf.resize(budget);
379 escape_field_to_buffer(f.value());
380 }
381 // TODO: Support deleter template argument on unique_ptr.
382 else if constexpr (
383 std::is_same_v<Field, std::unique_ptr<std::string>> or
384 std::is_same_v<Field, std::unique_ptr<std::string_view>> or
385 std::is_same_v<Field, std::unique_ptr<zview>> or
386 std::is_same_v<Field, std::shared_ptr<std::string>> or
387 std::is_same_v<Field, std::shared_ptr<std::string_view>> or
388 std::is_same_v<Field, std::shared_ptr<zview>>)
389 {
390 // TODO: Can we generalise this elegantly without Concepts?
391 // Effectively also an optional string. It's not null (we checked
392 // for that above).
393 m_field_buf.resize(budget);
394 escape_field_to_buffer(*f);
395 }
396 else
397 {
398 // This field needs to be converted to a string, and after that,
399 // escaped as well.
400 m_field_buf.resize(budget);
401 auto const data{m_field_buf.data()};
402 escape_field_to_buffer(
403 traits::to_buf(data, data + std::size(m_field_buf), f));
404 }
405 }
406 }
407
409
415 template<typename Field>
416 std::enable_if_t<nullness<Field>::always_null>
417 append_to_buffer(Field const &)
418 {
419 m_buffer.append(null_field);
420 }
421
423 template<typename Container>
424 std::enable_if_t<not std::is_same_v<typename Container::value_type, char>>
425 fill_buffer(Container const &c)
426 {
427 // To avoid unnecessary allocations and deallocations, we run through c
428 // twice: once to determine how much buffer space we may need, and once to
429 // actually write it into the buffer.
430 std::size_t budget{0};
431 for (auto const &f : c) budget += estimate_buffer(f);
432 m_buffer.reserve(budget);
433 for (auto const &f : c) append_to_buffer(f);
434 }
435
437 template<typename Tuple, std::size_t... indexes>
438 static std::size_t
439 budget_tuple(Tuple const &t, std::index_sequence<indexes...>)
440 {
441 return (estimate_buffer(std::get<indexes>(t)) + ...);
442 }
443
445 template<typename Tuple, std::size_t... indexes>
446 void append_tuple(Tuple const &t, std::index_sequence<indexes...>)
447 {
448 (append_to_buffer(std::get<indexes>(t)), ...);
449 }
450
452 template<typename... Elts> void fill_buffer(std::tuple<Elts...> const &t)
453 {
454 using indexes = std::make_index_sequence<sizeof...(Elts)>;
455
456 m_buffer.reserve(budget_tuple(t, indexes{}));
457 append_tuple(t, indexes{});
458 }
459
461 template<typename... Ts> void fill_buffer(const Ts &...fields)
462 {
463 (..., append_to_buffer(fields));
464 }
465
466 constexpr static std::string_view s_classname{"stream_to"};
467};
468
469
470template<typename Columns>
472 transaction_base &tx, std::string_view table_name, Columns const &columns) :
473 stream_to{tx, table_name, std::begin(columns), std::end(columns)}
474{}
475
476
477template<typename Iter>
479 transaction_base &tx, std::string_view table_name, Iter columns_begin,
480 Iter columns_end) :
481 stream_to{
482 tx,
483 tx.quote_name(
484 table_name,
485 separated_list(",", columns_begin, columns_end, [&tx](auto col) {
486 return tx.quote_name(*col);
487 }))}
488{}
489} // namespace pqxx
490#endif
The home of all libpqxx classes, functions, templates, etc.
Definition array.hxx:33
std::string separated_list(std::string_view sep, ITER begin, ITER end, ACCESS access)
Represent sequence of values as a string, joined by a given separator.
Definition separated_list.hxx:44
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:509
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:519
std::initializer_list< std::string_view > table_path
Representation of a PostgreSQL table path.
Definition connection.hxx:185
constexpr bool is_null(TYPE const &value) noexcept
Is value null?
Definition strconv.hxx:508
Reference to one row in a result.
Definition row.hxx:47
Stream data from the database.
Definition stream_from.hxx:79
Efficiently write data directly to a database table.
Definition stream_to.hxx:81
static stream_to raw_table(transaction_base &tx, std::string_view path, std::string_view columns="")
Stream data to a pre-quoted table and columns.
Definition stream_to.hxx:104
constexpr bool operator!() const noexcept
Has this stream been through its concluding complete()?
Definition stream_to.hxx:210
static stream_to table(transaction_base &tx, table_path path, std::initializer_list< std::string_view > columns={})
Create a stream_to writing to a named table and columns.
Definition stream_to.hxx:120
void write_values(Ts const &...fields)
Insert values as a row.
Definition stream_to.hxx:264
stream_to(transaction_base &tx, std::string_view table_name)
Create a stream, without specifying columns.
Definition stream_to.hxx:172
stream_to(stream_to &&other)
Definition stream_to.hxx:192
stream_to & operator<<(Row const &row)
Insert a row of data.
Definition stream_to.hxx:234
void write_row(Row const &row)
Insert a row of data, given in the form of a std::tuple or container.
Definition stream_to.hxx:254
Interface definition (and common code) for "transaction" classes.
Definition transaction_base.hxx:88
constexpr connection & conn() const noexcept
The connection in which this transaction lives.
Definition transaction_base.hxx:792
std::string quote_name(std::string_view identifier) const
Escape an SQL identifier for use in a query.
Definition transaction_base.hxx:231
Base class for things that monopolise a transaction's attention.
Definition transaction_focus.hxx:29