libpqxx
The C++ client library for PostgreSQL
Loading...
Searching...
No Matches
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-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 <optional>
23
24
25namespace pqxx
26{
28
81class PQXX_LIBEXPORT stream_to : transaction_focus
82{
83public:
85
105 static stream_to raw_table(
106 transaction_base &tx, std::string_view path, std::string_view columns = "")
107 {
108 return {tx, path, columns};
109 }
110
112
121 static stream_to table(
122 transaction_base &tx, table_path path,
123 std::initializer_list<std::string_view> columns = {})
124 {
125 auto const &cx{tx.conn()};
126 return raw_table(tx, cx.quote_table(path), cx.quote_columns(columns));
127 }
128
129#if defined(PQXX_HAVE_CONCEPTS)
131
138 template<PQXX_CHAR_STRINGS_ARG COLUMNS>
139 static stream_to
140 table(transaction_base &tx, table_path path, COLUMNS const &columns)
141 {
142 auto const &cx{tx.conn()};
143 return stream_to::raw_table(
144 tx, cx.quote_table(path), tx.conn().quote_columns(columns));
145 }
146
148
155 template<PQXX_CHAR_STRINGS_ARG COLUMNS>
156 static stream_to
157 table(transaction_base &tx, std::string_view path, COLUMNS const &columns)
158 {
159 return stream_to::raw_table(tx, path, tx.conn().quote_columns(columns));
160 }
161#endif // PQXX_HAVE_CONCEPTS
162
163 explicit stream_to(stream_to &&other) :
164 // (This first step only moves the transaction_focus base-class
165 // object.)
166 transaction_focus{std::move(other)},
167 m_finished{other.m_finished},
168 m_buffer{std::move(other.m_buffer)},
169 m_field_buf{std::move(other.m_field_buf)},
170 m_finder{other.m_finder}
171 {
172 other.m_finished = true;
173 }
174 ~stream_to() noexcept;
175
177 [[nodiscard]] constexpr operator bool() const noexcept
178 {
179 return not m_finished;
180 }
182 [[nodiscard]] constexpr bool operator!() const noexcept
183 {
184 return m_finished;
185 }
186
188
194 void complete();
195
197
206 template<typename Row> stream_to &operator<<(Row const &row)
207 {
208 write_row(row);
209 return *this;
210 }
211
213
217 stream_to &operator<<(stream_from &);
218
220
226 template<typename Row> void write_row(Row const &row)
227 {
228 fill_buffer(row);
229 write_buffer();
230 }
231
233
236 template<typename... Ts> void write_values(Ts const &...fields)
237 {
238 fill_buffer(fields...);
239 write_buffer();
240 }
241
243
252 [[deprecated("Use table() or raw_table() factory.")]] stream_to(
253 transaction_base &tx, std::string_view table_name) :
254 stream_to{tx, table_name, ""sv}
255 {}
256
258
260 template<typename Columns>
261 [[deprecated("Use table() or raw_table() factory.")]] stream_to(
262 transaction_base &, std::string_view table_name, Columns const &columns);
263
264private:
266 stream_to(
267 transaction_base &tx, std::string_view path, std::string_view columns);
268
269 bool m_finished = false;
270
272 std::string m_buffer;
273
275 std::string m_field_buf;
276
278 internal::char_finder_func *m_finder;
279
281 void write_raw_line(std::string_view);
282
284
286 void write_buffer();
287
289 static constexpr std::string_view null_field{"\\N\t"};
290
292 template<typename T>
293 static std::enable_if_t<nullness<T>::always_null, std::size_t>
294 estimate_buffer(T const &)
295 {
296 return std::size(null_field);
297 }
298
300
303 template<typename T>
304 static std::enable_if_t<not nullness<T>::always_null, std::size_t>
305 estimate_buffer(T const &field)
306 {
307 return is_null(field) ? std::size(null_field) : size_buffer(field);
308 }
309
311 void escape_field_to_buffer(std::string_view data);
312
314
320 template<typename Field>
321 std::enable_if_t<not nullness<Field>::always_null>
322 append_to_buffer(Field const &f)
323 {
324 // We append each field, terminated by a tab. That will leave us with
325 // one tab too many, assuming we write any fields at all; we remove that
326 // at the end.
327 if (is_null(f))
328 {
329 // Easy. Append null and tab in one go.
330 m_buffer.append(null_field);
331 }
332 else
333 {
334 // Convert f into m_buffer.
335
336 using traits = string_traits<Field>;
337 auto const budget{estimate_buffer(f)};
338 auto const offset{std::size(m_buffer)};
339
340 if constexpr (std::is_arithmetic_v<Field>)
341 {
342 // Specially optimised for "safe" types, which never need any
343 // escaping. Convert straight into m_buffer.
344
345 // The budget we get from size_buffer() includes room for the trailing
346 // zero, which we must remove. But we're also inserting tabs between
347 // fields, so we re-purpose the extra byte for that.
348 auto const total{offset + budget};
349 m_buffer.resize(total);
350 auto const data{m_buffer.data()};
351 char *const end{traits::into_buf(data + offset, data + total, f)};
352 *(end - 1) = '\t';
353 // Shrink to fit. Keep the tab though.
354 m_buffer.resize(static_cast<std::size_t>(end - data));
355 }
356 else if constexpr (
357 std::is_same_v<Field, std::string> or
358 std::is_same_v<Field, std::string_view> or
359 std::is_same_v<Field, zview>)
360 {
361 // This string may need escaping.
362 m_field_buf.resize(budget);
363 escape_field_to_buffer(f);
364 }
365 else if constexpr (
366 std::is_same_v<Field, std::optional<std::string>> or
367 std::is_same_v<Field, std::optional<std::string_view>> or
368 std::is_same_v<Field, std::optional<zview>>)
369 {
370 // Optional string. It's not null (we checked for that above), so...
371 // Treat like a string.
372 m_field_buf.resize(budget);
373 escape_field_to_buffer(f.value());
374 }
375 // TODO: Support deleter template argument on unique_ptr.
376 else if constexpr (
377 std::is_same_v<Field, std::unique_ptr<std::string>> or
378 std::is_same_v<Field, std::unique_ptr<std::string_view>> or
379 std::is_same_v<Field, std::unique_ptr<zview>> or
380 std::is_same_v<Field, std::shared_ptr<std::string>> or
381 std::is_same_v<Field, std::shared_ptr<std::string_view>> or
382 std::is_same_v<Field, std::shared_ptr<zview>>)
383 {
384 // TODO: Can we generalise this elegantly without Concepts?
385 // Effectively also an optional string. It's not null (we checked
386 // for that above).
387 m_field_buf.resize(budget);
388 escape_field_to_buffer(*f);
389 }
390 else
391 {
392 // This field needs to be converted to a string, and after that,
393 // escaped as well.
394 m_field_buf.resize(budget);
395 auto const data{m_field_buf.data()};
396 escape_field_to_buffer(
397 traits::to_buf(data, data + std::size(m_field_buf), f));
398 }
399 }
400 }
401
403
409 template<typename Field>
410 std::enable_if_t<nullness<Field>::always_null>
411 append_to_buffer(Field const &)
412 {
413 m_buffer.append(null_field);
414 }
415
417 template<typename Container>
418 std::enable_if_t<not std::is_same_v<typename Container::value_type, char>>
419 fill_buffer(Container const &c)
420 {
421 // To avoid unnecessary allocations and deallocations, we run through c
422 // twice: once to determine how much buffer space we may need, and once to
423 // actually write it into the buffer.
424 std::size_t budget{0};
425 for (auto const &f : c) budget += estimate_buffer(f);
426 m_buffer.reserve(budget);
427 for (auto const &f : c) append_to_buffer(f);
428 }
429
431 template<typename Tuple, std::size_t... indexes>
432 static std::size_t
433 budget_tuple(Tuple const &t, std::index_sequence<indexes...>)
434 {
435 return (estimate_buffer(std::get<indexes>(t)) + ...);
436 }
437
439 template<typename Tuple, std::size_t... indexes>
440 void append_tuple(Tuple const &t, std::index_sequence<indexes...>)
441 {
442 (append_to_buffer(std::get<indexes>(t)), ...);
443 }
444
446 template<typename... Elts> void fill_buffer(std::tuple<Elts...> const &t)
447 {
448 using indexes = std::make_index_sequence<sizeof...(Elts)>;
449
450 m_buffer.reserve(budget_tuple(t, indexes{}));
451 append_tuple(t, indexes{});
452 }
453
455 template<typename... Ts> void fill_buffer(const Ts &...fields)
456 {
457 (..., append_to_buffer(fields));
458 }
459
460 constexpr static std::string_view s_classname{"stream_to"};
461};
462
463
464template<typename Columns>
465inline stream_to::stream_to(
466 transaction_base &tx, std::string_view table_name, Columns const &columns) :
467 stream_to{tx, table_name, std::begin(columns), std::end(columns)}
468{}
469} // namespace pqxx
470#endif
Base class for things that monopolise a transaction's attention.
Definition transaction_focus.hxx:29
#define PQXX_LIBEXPORT
Definition header-pre.hxx:157
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:527
constexpr bool is_null(TYPE const &value) noexcept
Is value null?
Definition strconv.hxx:516