libpqxx
The C++ client library for PostgreSQL
stream_to.hxx
Go to the documentation of this file.
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-2026, 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_STREAM_TO_HXX
14 #define PQXX_STREAM_TO_HXX
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"
22 
23 
24 namespace pqxx
25 {
27 
81 {
82 public:
84 
93  static stream_to table(
94  transaction_base &tx, table_path path,
95  std::initializer_list<std::string_view> columns = {})
96  {
97  auto const &cx{tx.conn()};
98  return raw_table(tx, cx.quote_table(path), cx.quote_columns(columns));
99  }
100 
102 
109  template<pqxx::char_strings COLUMNS>
110  static stream_to
111  table(transaction_base &tx, table_path path, COLUMNS const &columns)
112  {
113  auto const &cx{tx.conn()};
114  return stream_to::raw_table(
115  tx, cx.quote_table(path), tx.conn().quote_columns(columns));
116  }
117 
119 
126  template<pqxx::char_strings COLUMNS>
127  static stream_to
128  table(transaction_base &tx, std::string_view path, COLUMNS const &columns)
129  {
130  return stream_to::raw_table(tx, path, tx.conn().quote_columns(columns));
131  }
132 
134 
155  transaction_base &tx, std::string_view path, std::string_view columns = "",
156  sl loc = sl::current())
157  {
158  return {tx, path, columns, loc};
159  }
160 
161  stream_to(stream_to const &) = delete;
162 
163  // Some false positives from clang-tidy checks in this constructor. The
164  // initial base-class move moves only the embedded base-class object, not the
165  // individual member variables that fall outside the base class.
166  // Also there's one deliberate invalidation step of the moved-from object,
167  // which is also being flagged as a potential use-after-move.
168 
169  // NOLINTBEGIN(bugprone-use-after-move,hicpp-invalid-access-moved)
170  stream_to(stream_to &&other) :
171  // (This first step only moves the transaction_focus base-class
172  // object.)
173  transaction_focus{std::move(other)},
174  m_buffer{std::move(other.m_buffer)},
175  m_field_buf{std::move(other.m_field_buf)},
176  m_finder{other.m_finder},
177  m_created_loc{other.m_created_loc},
178  m_finished{other.m_finished}
179  {
180  other.m_finished = true;
181  }
182  // NOLINTEND(bugprone-use-after-move,hicpp-invalid-access-moved)
183 
184  ~stream_to() noexcept;
185 
186  stream_to &operator=(stream_to const &) = delete;
187  stream_to &operator=(stream_to &&) = delete;
188 
189  // NOLINTBEGIN(google-explicit-constructor,hicpp-explicit-conversions)
191  [[nodiscard]] constexpr operator bool() const noexcept
192  {
193  return not m_finished;
194  }
195  // NOLINTEND(google-explicit-constructor,hicpp-explicit-conversions)
196 
198  [[nodiscard]] constexpr bool operator!() const noexcept
199  {
200  return m_finished;
201  }
202 
204 
210  void complete(sl loc = sl::current());
211 
213 
222  template<typename Row> stream_to &operator<<(Row const &row)
223  {
224  write_row(row, m_created_loc);
225  return *this;
226  }
227 
229 
234 
236 
242  template<typename Row> void write_row(Row const &row, sl loc = sl::current())
243  {
244  fill_buffer(
245  row, conversion_context{trans().conn().get_encoding_group(loc), loc});
246  write_buffer(loc);
247  }
248 
250 
253  template<typename... Ts> void write_values(Ts const &...fields)
254  {
255  fill_buffer(fields...);
256  write_buffer(m_created_loc);
257  }
258 
259 private:
261  stream_to(
262  transaction_base &tx, std::string_view path, std::string_view columns, sl);
263 
265  std::string m_buffer;
266 
268  std::string m_field_buf;
269 
271  internal::char_finder_func *m_finder;
272 
274  sl m_created_loc;
275 
277  bool m_finished = false;
278 
280  void write_raw_line(std::string_view, sl);
281 
283 
285  void write_buffer(sl);
286 
288  static constexpr std::string_view null_field{"\\N\t"};
289 
291  template<typename T>
292  static constexpr std::size_t estimate_buffer(T const &)
293  requires(pqxx::always_null<T>())
294  {
295  return std::size(null_field);
296  }
297 
299 
302  template<typename T>
303  static constexpr std::size_t estimate_buffer(T const &field)
304  requires(not pqxx::always_null<T>())
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, sl loc);
311 
313 
319  template<typename Field>
320  void append_to_buffer(Field const &f, ctx c)
321  requires(not pqxx::always_null<Field>())
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  auto const budget{estimate_buffer(f)};
335 
336  // We're not using is_unquoted_safe for this, because in this context,
337  // a tab counts as a special character that needs escaping.
338  if constexpr (std::is_arithmetic_v<Field>)
339  {
340  // Specially optimised for "safe" types, which never need any
341  // escaping. Convert straight into m_buffer.
342 
343  auto const offset{std::size(m_buffer)};
344  // Also include room to append the tab.
345  auto const total{offset + budget + 1};
346  m_buffer.resize(total);
347  auto const data{m_buffer.data()};
348  std::size_t const end{
349  offset + into_buf({data + offset, data + total}, f, c)};
350  assert((end + 1) < std::size(m_buffer));
351  m_buffer[end] = '\t';
352  // Shrink to fit. Keep the tab though.
353  m_buffer.resize(end + 1);
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, c.loc);
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  // No need to check whether f has a value; if it didn't, the is_null(f)
373  // check at the top would have evaluated as true.
374  // NOLINTNEXTLINE(bugprone-unchecked-optional-access)
375  escape_field_to_buffer(f.value(), c.loc);
376  }
377  // TODO: Support deleter template argument on unique_ptr.
378  else if constexpr (
379  std::is_same_v<Field, std::unique_ptr<std::string>> or
380  std::is_same_v<Field, std::unique_ptr<std::string_view>> or
381  std::is_same_v<Field, std::unique_ptr<zview>> or
382  std::is_same_v<Field, std::shared_ptr<std::string>> or
383  std::is_same_v<Field, std::shared_ptr<std::string_view>> or
384  std::is_same_v<Field, std::shared_ptr<zview>>)
385  {
386  // TODO: Generalise this.
387  // Effectively also an optional string. It's not null (we checked
388  // for that above).
389  m_field_buf.resize(budget);
390  escape_field_to_buffer(*f, c.loc);
391  }
392  else
393  {
394  // This field needs to be converted to a string, and after that,
395  // escaped as well.
396  m_field_buf.resize(budget);
397  escape_field_to_buffer(to_buf(m_field_buf, f, c), c.loc);
398  }
399  }
400  }
401 
403 
409  template<typename Field>
410  void append_to_buffer(Field const &, ctx)
411  requires(pqxx::always_null<Field>())
412  {
413  m_buffer.append(null_field);
414  }
415 
417  template<typename Container>
418  void fill_buffer(Container const &cont, ctx c)
419  requires(not std::is_same_v<
420  std::remove_cv_t<typename Container::value_type>, char>)
421  {
422  // To avoid unnecessary allocations and deallocations, we run through c
423  // twice: once to determine how much buffer space we may need, and once to
424  // actually write it into the buffer.
425  std::size_t budget{0};
426  for (auto const &f : cont) budget += estimate_buffer(f);
427  m_buffer.reserve(budget);
428  for (auto const &f : cont) append_to_buffer(f, c);
429  }
430 
432  template<typename Tuple, std::size_t... indexes>
433  static std::size_t
434  budget_tuple(Tuple const &t, std::index_sequence<indexes...>)
435  {
436  return (estimate_buffer(std::get<indexes>(t)) + ...);
437  }
438 
440  template<typename Tuple, std::size_t... indexes>
441  void append_tuple(Tuple const &t, std::index_sequence<indexes...>, ctx c)
442  {
443  (append_to_buffer(std::get<indexes>(t), c), ...);
444  }
445 
447  template<typename... Elts>
448  void fill_buffer(std::tuple<Elts...> const &t, ctx c)
449  {
450  using indexes = std::make_index_sequence<sizeof...(Elts)>;
451 
452  m_buffer.reserve(budget_tuple(t, indexes{}));
453  append_tuple(t, indexes{}, c);
454  }
455 
457  template<typename... Ts> void fill_buffer(const Ts &...fields)
458  {
459  conversion_context const c{
460  trans().conn().get_encoding_group(m_created_loc), m_created_loc};
461  (..., append_to_buffer(fields, c));
462  }
463 
464  static constexpr std::string_view s_classname{"stream_to"};
465 };
466 } // namespace pqxx
467 #endif
std::string quote_columns(STRINGS const &columns, sl=sl::current()) const
Quote and comma-separate a series of column names.
Definition: connection.hxx:1610
Reference to one row in a result.
Definition: row.hxx:415
Stream data from the database.
Definition: stream_from.hxx:79
Efficiently write data directly to a database table.
Definition: stream_to.hxx:81
stream_to & operator<<(Row const &row)
Insert a row of data.
Definition: stream_to.hxx:222
constexpr bool operator!() const noexcept
Has this stream been through its concluding complete()?
Definition: stream_to.hxx:198
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:93
void write_values(Ts const &...fields)
Insert values as a row.
Definition: stream_to.hxx:253
static stream_to table(transaction_base &tx, table_path path, COLUMNS const &columns)
Create a stream_to writing to a named table and columns.
Definition: stream_to.hxx:111
void write_row(Row const &row, sl loc=sl::current())
Insert a row of data, given in the form of a std::tuple or container.
Definition: stream_to.hxx:242
stream_to(stream_to &&other)
Definition: stream_to.hxx:170
stream_to(stream_to const &)=delete
static stream_to table(transaction_base &tx, std::string_view path, COLUMNS const &columns)
Create a stream_to writing to a named table and columns.
Definition: stream_to.hxx:128
static stream_to raw_table(transaction_base &tx, std::string_view path, std::string_view columns="", sl loc=sl::current())
Stream data to a pre-quoted table and columns.
Definition: stream_to.hxx:154
Base class for things that monopolise a transaction's attention.
Definition: transaction_focus.hxx:29
constexpr connection & conn() const noexcept
The connection in which this transaction lives.
Definition: transaction_base.hxx:1118
Interface definition (and common code) for "transaction" classes.
Definition: transaction_base.hxx:151
#define PQXX_LIBEXPORT
Definition: header-pre.hxx:206
std::size_t(std::string_view haystack, std::size_t start, sl) char_finder_func
Function type: "find first occurrence of any of these ASCII characters.".
Definition: encoding_group.hxx:110
The home of all libpqxx classes, functions, templates, etc.
Definition: array.cxx:26
std::string_view to_buf(std::span< char > buf, TYPE const &value, ctx c={})
Represent value as SQL text, optionally using buf as storage.
Definition: strconv.hxx:430
std::source_location sl
Convenience alias for std::source_location. It's just too long.
Definition: types.hxx:38
std::initializer_list< std::string_view > table_path
Representation of a PostgreSQL table path.
Definition: connection.hxx:240
constexpr 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:399
constexpr bool is_null(TYPE const &value) noexcept
Is value a null?
Definition: strconv.hxx:764
requires(pqxx::internal::to_buf_7< TYPE > or pqxx::internal::to_buf_8< TYPE >) const eval bool supports_to_buf_8()
Is the libpqxx 8 version of to_buf() supported for TYPE?
Definition: strconv.hxx:417
std::size_t into_buf(std::span< char > buf, TYPE const &value, ctx c={})
Write an SQL representation of value into buf.
Definition: strconv.hxx:454
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:820
conversion_context const & ctx
Convenience alias: const reference to a pqxx::conversion_context.
Definition: strconv.hxx:201
Contextual parameters for string conversions implementations.
Definition: strconv.hxx:163