libpqxx
The C++ client library for PostgreSQL
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-2025, 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 
24 namespace pqxx
25 {
27 
80 class PQXX_LIBEXPORT stream_to : transaction_focus
81 {
82 public:
84 
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)
129 
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 
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 
263 private:
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 
463 template<typename Columns>
464 inline 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
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
void write_values(Ts const &...fields)
Insert values as a row.
Definition: stream_to.hxx:235
Efficiently write data directly to a database table.
Definition: stream_to.hxx:80
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
Reference to one row in a result.
Definition: row.hxx:46
Base class for things that monopolise a transaction's attention.
Definition: transaction_focus.hxx:28
constexpr connection & conn() const noexcept
The connection in which this transaction lives.
Definition: transaction_base.hxx:1005
std::vector< std::string_view > to_buf(char *here, char const *end, TYPE...value)
Convert multiple values to strings inside a single buffer.
Definition: strconv.hxx:493
constexpr bool is_null(TYPE const &value) noexcept
Is value null?
Definition: strconv.hxx:515
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
stream_to & operator<<(Row const &row)
Insert a row of data.
Definition: stream_to.hxx:205
Stream data from the database.
Definition: stream_from.hxx:78
std::initializer_list< std::string_view > table_path
Representation of a PostgreSQL table path.
Definition: connection.hxx:231
The home of all libpqxx classes, functions, templates, etc.
Definition: array.cxx:26
stream_to(transaction_base &tx, std::string_view table_name)
Create a stream, without specifying columns.
Definition: stream_to.hxx:251
std::size_t(std::string_view haystack, std::size_t start) char_finder_func
Function type: "find first occurrence of specific any of ASCII characters.".
Definition: encoding_group.hxx:71
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:225
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
Interface definition (and common code) for "transaction" classes.
Definition: transaction_base.hxx:150
constexpr bool operator!() const noexcept
Has this stream been through its concluding complete()?
Definition: stream_to.hxx:181