Adam Ierymenko 0e5651f353
1.12.0 merge to main (#2104)
* add note about forceTcpRelay

* Create a sample systemd unit for tcp proxy

* set gitattributes for rust & cargo so hashes dont conflict on Windows

* Revert "set gitattributes for rust & cargo so hashes dont conflict on Windows"

This reverts commit 032dc5c108195f6bbc2e224f00da5b785df4b7f9.

* Turn off autocrlf for rust source

Doesn't appear to play nice well when it comes to git and vendored cargo package hashes

* Fix #1883 (#1886)

Still unknown as to why, but the call to `nc->GetProperties()` can fail
when setting a friendly name on the Windows virtual ethernet adapter.
Ensure that `ncp` is not null before continuing and accessing the device
GUID.

* Don't vendor packages for zeroidc (#1885)

* Added docker environment way to join networks (#1871)

* add StringUtils

* fix headers
use recommended headers and remove unused headers

* move extern "C"
only JNI functions need to be exported

* cleanup

* fix ANDROID-50: RESULT_ERROR_BAD_PARAMETER typo

* fix typo in log message

* fix typos in JNI method signatures

* fix typo

* fix ANDROID-51: fieldName is uninitialized

* fix ANDROID-35: memory leak

* fix missing DeleteLocalRef in loops

* update to use unique error codes

* add GETENV macro

* add LOG_TAG defines

* ANDROID-48: add ZT_jnicache.cpp

* ANDROID-48: use ZT_jnicache.cpp and remove ZT_jnilookup.cpp and ZT_jniarray.cpp

* add Event.fromInt

* add PeerRole.fromInt

* add ResultCode.fromInt

* fix ANDROID-36: issues with ResultCode

* add VirtualNetworkConfigOperation.fromInt

* fix ANDROID-40: VirtualNetworkConfigOperation out-of-sync with ZT_VirtualNetworkConfigOperation enum

* add VirtualNetworkStatus.fromInt

* fix ANDROID-37: VirtualNetworkStatus out-of-sync with ZT_VirtualNetworkStatus enum

* add VirtualNetworkType.fromInt

* make NodeStatus a plain data class

* fix ANDROID-52: synchronization bug with nodeMap

* Node init work: separate Node construction and init

* add Node.toString

* make PeerPhysicalPath a plain data class

* remove unused PeerPhysicalPath.fixed

* add array functions

* make Peer a plain data class

* make Version a plain data class

* fix ANDROID-42: copy/paste error

* fix ANDROID-49: VirtualNetworkConfig.equals is wrong

* reimplement VirtualNetworkConfig.equals

* reimplement VirtualNetworkConfig.compareTo

* add VirtualNetworkConfig.hashCode

* make VirtualNetworkConfig a plain data class

* remove unused VirtualNetworkConfig.enabled

* reimplement VirtualNetworkDNS.equals

* add VirtualNetworkDNS.hashCode

* make VirtualNetworkDNS a plain data class

* reimplement VirtualNetworkRoute.equals

* reimplement VirtualNetworkRoute.compareTo

* reimplement VirtualNetworkRoute.toString

* add VirtualNetworkRoute.hashCode

* make VirtualNetworkRoute a plain data class

* add isSocketAddressEmpty

* add addressPort

* add fromSocketAddressObject

* invert logic in a couple of places and return early

* newInetAddress and newInetSocketAddress work
allow newInetSocketAddress to return NULL if given empty address

* fix ANDROID-38: stack corruption in onSendPacketRequested

* use GETENV macro

* JniRef work
JniRef does not use callbacks struct, so remove
fix NewGlobalRef / DeleteGlobalRef mismatch

* use PRId64 macros

* switch statement work

* comments and logging

* Modifier 'public' is redundant for interface members

* NodeException can be made a checked Exception

* 'NodeException' does not define a 'serialVersionUID' field

* 'finalize()' should not be overridden
this is fine to do because ZeroTierOneService calls close() when it is done

* error handling, error reporting, asserts, logging

* simplify loadLibrary

* rename Node.networks -> Node.networkConfigs

* Windows file permissions fix (#1887)

* Allow macOS interfaces to use multiple IP addresses (#1879)

Co-authored-by: Sean OMeara <someara@users.noreply.github.com>
Co-authored-by: Grant Limberg <glimberg@users.noreply.github.com>

* Fix condition where full HELLOs might not be sent when necessary (#1877)

Co-authored-by: Grant Limberg <glimberg@users.noreply.github.com>

* 1.10.4 version bumps

* Add security policy to repo (#1889)

* [+] add e2k64 arch (#1890)

* temp fix for ANDROID-56: crash inside newNetworkConfig from too many args

* 1.10.4 release notes

* Windows 1.10.4 Advanced Installer bump

* Revert "temp fix for ANDROID-56: crash inside newNetworkConfig from too many args"

This reverts commit dd627cd7f44ad623a110bb14f72d0bea72a09e30.

* actual fix for ANDROID-56: crash inside newNetworkConfig
cast all arguments to varargs functions as good style

* Fix addIp being called with applied ips (#1897)

This was getting called outside of the check for existing ips
Because of the added ifdef and a brace getting moved to the
wrong place.

```
if (! n.tap()->addIp(*ip)) {
	fprintf(stderr, "ERROR: unable to add ip address %s" ZT_EOL_S, ip->toString(ipbuf));
}
WinFWHelper::newICMPRule(*ip, n.config().nwid);

```

* 1.10.5 (#1905)

* 1.10.5 bump

* 1.10.5 for Windows

* 1.10.5

* Prevent path-learning loops (#1914)

* Prevent path-learning loops

* Only allow new overwrite if not bonded

* fix binding temporary ipv6 addresses on macos (#1910)

The check code wasn't running.

I don't know why !defined(TARGET_OS_IOS) would exclude code on
desktop macOS. I did a quick search and changed it to defined(TARGET_OS_MAC).
Not 100% sure what the most correct solution there is.

You can verify the old and new versions with

`ifconfig | grep temporary`

plus

`zerotier-cli info -j` -> listeningOn

* 1.10.6 (#1929)

* 1.10.5 bump

* 1.10.6

* 1.10.6 AIP for Windows.

* Release notes for 1.10.6 (#1931)

* Minor tweak to Synology Docker image script (#1936)

* Change if_def again so ios can build (#1937)

All apple's variables are "defined"
but sometimes they are defined as "0"

* move begin/commit into try/catch block (#1932)

Thread was exiting in some cases

* Bump openssl from 0.10.45 to 0.10.48 in /zeroidc (#1938)

Bumps [openssl](https://github.com/sfackler/rust-openssl) from 0.10.45 to 0.10.48.
- [Release notes](https://github.com/sfackler/rust-openssl/releases)
- [Commits](https://github.com/sfackler/rust-openssl/compare/openssl-v0.10.45...openssl-v0.10.48)

---
updated-dependencies:
- dependency-name: openssl
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* new drone bits

* Fix multiple network join from environment entrypoint.sh.release (#1961)

* _bond_m guards _bond, not _paths_m (#1965)

* Fix: warning: mutex '_aqm_m' is not held on every path through here [-Wthread-safety-analysis] (#1964)

* Bump h2 from 0.3.16 to 0.3.17 in /zeroidc (#1963)

Bumps [h2](https://github.com/hyperium/h2) from 0.3.16 to 0.3.17.
- [Release notes](https://github.com/hyperium/h2/releases)
- [Changelog](https://github.com/hyperium/h2/blob/master/CHANGELOG.md)
- [Commits](https://github.com/hyperium/h2/compare/v0.3.16...v0.3.17)

---
updated-dependencies:
- dependency-name: h2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Grant Limberg <glimberg@users.noreply.github.com>

* Add note that binutils is required on FreeBSD (#1968)

* Add prometheus metrics for Central controllers (#1969)

* add header-only prometheus lib to ext

* rename folder

* Undo rename directory

* prometheus simpleapi included on mac & linux

* wip

* wire up some controller stats

* Get windows building with prometheus

* bsd build flags for prometheus

* Fix multiple network join from environment entrypoint.sh.release (#1961)

* _bond_m guards _bond, not _paths_m (#1965)

* Fix: warning: mutex '_aqm_m' is not held on every path through here [-Wthread-safety-analysis] (#1964)

* Serve prom metrics from /metrics endpoint

* Add prom metrics for Central controller specific things

* reorganize metric initialization

* testing out a labled gauge on Networks

* increment error counter on throw

* Consolidate metrics definitions

Put all metric definitions into node/Metrics.hpp.  Accessed as needed
from there.

* Revert "testing out a labled gauge on Networks"

This reverts commit 499ed6d95e11452019cdf48e32ed4cd878c2705b.

* still blows up but adding to the record for completeness right now

* Fix runtime issues with metrics

* Add metrics files to visual studio project

* Missed an "extern"

* add copyright headers to new files

* Add metrics for sent/received bytes (total)

* put /metrics endpoint behind auth

* sendto returns int on Win32

---------

Co-authored-by: Leonardo Amaral <leleobhz@users.noreply.github.com>
Co-authored-by: Brenton Bostick <bostick@gmail.com>

* Central startup update (#1973)

* allow specifying authtoken in central startup

* set allowManagedFrom

* move redis_mem_notification to the correct place

* add node checkins metric

* wire up min/max connection pool size metrics

* x86_64-unknown-linux-gnu on ubuntu runner (#1975)

* adding incoming zt packet type metrics (#1976)

* use cpp-httplib for HTTP control plane (#1979)

refactored the old control plane code to use [cpp-httplib](https://github.com/yhirose/cpp-httplib) instead of a hand rolled HTTP server.  Makes the control plane code much more legible.  Also no longer randomly stops responding.

* Outgoing Packet Metrics (#1980)

add tx/rx labels to packet counters and add metrics for outgoing packets

* Add short-term validation test workflow (#1974)

Add short-term validation test workflow

* Brenton/curly braces (#1971)

* fix formatting

* properly adjust various lines
breakup multiple statements onto multiple lines

* insert {} around if, for, etc.

* Fix rust dependency caching (#1983)

* fun with rust caching

* kick

* comment out invalid yaml keys for now

* Caching should now work

* re-add/rename key directives

* bump

* bump

* bump

* Don't force rebuild on Windows build GH Action (#1985)

Switching `/t:ZeroTierOne:Rebuild` to just `/t:ZeroTierOne` allows the Windows build to use the rust cache.  `/t:ZeroTierOne:Rebuild` cleared the cache before building.

* More packet metrics (#1982)

* found path negotation sends that weren't accounted for

* Fix histogram so it will actually compile

* Found more places for packet metrics

* separate the bind & listen calls on the http backplane (#1988)

* fix memory leak (#1992)

* fix a couple of metrics (#1989)

* More aggressive CLI spamming (#1993)

* fix type signatures (#1991)

* Network-metrics (#1994)

* Add a couple quick functions for converting a uint64_t network ID/node ID into std::string

* Network metrics

* Peer metrics (#1995)

* Adding peer metrics

still need to be wired up for use

* per peer packet metrics

* Fix crash from bad instantiation of histogram

* separate alive & dead path counts

* Add peer metric update block

* add peer latency values in doPingAndKeepalive

* prevent deadlock

* peer latency histogram actually works now

* cleanup

* capture counts of packets to specific peers

---------

Co-authored-by: Joseph Henry <joseph.henry@zerotier.com>

* Metrics consolidation (#1997)

* Rename zt_packet_incoming -> zt_packet

Also consolidate zt_peer_packets into a single metric with tx and rx labels.  Same for ztc_tcp_data and ztc_udp_data

* Further collapse tcp & udp into metric labels for zt_data

* Fix zt_data metric description

* zt_peer_packets description fix

* Consolidate incoming/outgoing network packets to a single metric

* zt_incoming_packet_error -> zt_packet_error

* Disable peer metrics for central controllers

Can change in the future if needed, but given the traffic our controllers serve, that's going to be a *lot* of data

* Disable peer metrics for controllers pt 2

* Update readme files for metrics (#2000)

* Controller Metrics & Network Config Request Fix (#2003)

* add new metrics for network config request queue size and sso expirations
* move sso expiration to its own thread in the controller
* fix potential undefined behavior when modifying a set

* Enable RTTI in Windows build

The new prometheus histogram stuff needs it.

Access violation - no RTTI data!INVALID packet 636ebd9ee8cac6c0 from cafe9efeb9(2605:9880:200:1200:30:571:e34:51/9993) (unexpected exception in tryDecode())

* Don't re-apply routes on BSD

See issue #1986

* Capture setContent by-value instead of by-reference (#2006)

Co-authored-by: Grant Limberg <glimberg@users.noreply.github.com>

* fix typos (#2010)

* central controller metrics & request path updates (#2012)

* internal db metrics

* use shared mutexes for read/write locks

* remove this lock. only used for a metric

* more metrics

* remove exploratory metrics

place controller request benchmarks behind ifdef

* Improve validation test (#2013)

* fix init order for EmbeddedNetworkController (#2014)

* add constant for getifaddrs cache time

* cache getifaddrs - mac

* cache getifaddrs - linux

* cache getifaddrs - bsd

* cache getifaddrs - windows

* Fix oidc client lookup query

join condition referenced the wrong table.  Worked fine unless there were multiple identical client IDs

* Fix udp sent metric

was only incrementing by 1 for each packet sent

* Allow sending all surface addresses to peer in low-bandwidth mode

* allow enabling of low bandwidth mode on controllers

* don't unborrow bad connections

pool will clean them up later

* Multi-arch controller container (#2037)

create arm64 & amd64 images for central controller

* Update README.md

issue #2009

* docker tags change

* fix oidc auth url memory leak (#2031)

getAuthURL() was not calling zeroidc::free_cstr(url);

the only place authAuthURL is called, the url can be retrieved
from the network config instead.

You could alternatively copy the string and call free_cstr in getAuthURL.
If that's better we can change the PR.

Since now there are no callers of getAuthURL I deleted it.

Co-authored-by: Grant Limberg <glimberg@users.noreply.github.com>

* Bump openssl from 0.10.48 to 0.10.55 in /zeroidc (#2034)

Bumps [openssl](https://github.com/sfackler/rust-openssl) from 0.10.48 to 0.10.55.
- [Release notes](https://github.com/sfackler/rust-openssl/releases)
- [Commits](https://github.com/sfackler/rust-openssl/compare/openssl-v0.10.48...openssl-v0.10.55)

---
updated-dependencies:
- dependency-name: openssl
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Grant Limberg <glimberg@users.noreply.github.com>

* zeroidc cargo warnings (#2029)

* fix unused struct member cargo warning

* fix unused import cargo warning

* fix unused return value cargo warning

---------

Co-authored-by: Grant Limberg <glimberg@users.noreply.github.com>

* fix memory leak in macos ipv6/dns helper (#2030)

Co-authored-by: Grant Limberg <glimberg@users.noreply.github.com>

* Consider ZEROTIER_JOIN_NETWORKS in healthcheck (#1978)

* Add a 2nd auth token only for access to /metrics (#2043)

* Add a 2nd auth token for /metrics

Allows administrators to distribute a token that only has access to read
metrics and nothing else.

Also added support for using bearer auth tokens for both types of tokens

Separate endpoint for metrics #2041

* Update readme

* fix a couple of cases of writing the wrong token

* Add warning to cli for allow default on FreeBSD

It doesn't work.
Not possible to fix with deficient network
stack and APIs.

ZeroTierOne-freebsd # zerotier-cli set 9bee8941b5xxxxxx allowDefault=1
400 set Allow Default does not work properly on FreeBSD. See #580
root@freebsd13-a:~/ZeroTierOne-freebsd # zerotier-cli get 9bee8941b5xxxxxx allowDefault
1

* ARM64 Support for TapDriver6 (#1949)

* Release memory previously allocated by UPNP_GetValidIGD

* Fix ifdef that breaks libzt on iOS (#2050)

* less drone (#2060)

* Exit if loading an invalid identity from disk (#2058)

* Exit if loading an invalid identity from disk

Previously, if an invalid identity was loaded from disk, ZeroTier would
generate a new identity & chug along and generate a brand new identity
as if nothing happened.  When running in containers, this introduces the
possibility for key matter loss; especially when running in containers
where the identity files are mounted in the container read only.  In
this case, ZT will continue chugging along with a brand new identity
with no possibility of recovering the private key.

ZeroTier should exit upon loading of invalid identity.public/identity.secret #2056

* add validation test for #2056

* tcp-proxy: fix build

* Adjust tcp-proxy makefile to support metrics

There's no way to get the metrics yet. Someone will
have to add the http service.

* remove ZT_NO_METRIC ifdef

* Implement recvmmsg() for Linux to reduce syscalls. (#2046)

Between 5% and 40% speed improvement on Linux, depending on system configuration and load.

* suppress warnings: comparison of integers of different signs: 'int64_t' (aka 'long') and 'uint64_t' (aka 'unsigned long') [-Wsign-compare] (#2063)

* fix warning: 'OS_STRING' macro redefined [-Wmacro-redefined] (#2064)

Even though this is in ext, these particular chunks of code were added
by us, so are ok to modify.

* Apply default route a different way - macOS

The original way we applied default route, by forking
0.0.0.0/0 into 0/1 and 128/1 works, but if mac os has any networking
hiccups -if you change SSIDs or sleep/wake- macos erases the system default route.
And then all networking on the computer is broken.

to summarize the new way:
allowDefault=1
```
sudo route delete default 192.168.82.1
sudo route add default 10.2.0.2
sudo route add -ifscope en1 default 192.168.82.1
```

gives us this routing table
```
Destination        Gateway            RT_IFA             Flags        Refs      Use    Mtu          Netif Expire    rtt(ms) rttvar(ms)
default            10.2.0.2           10.2.0.18          UGScg          90        1   2800       feth4823
default            192.168.82.1       192.168.82.217     UGScIg
```

allowDefault=0
```
sudo route delete default
sudo route delete -ifscope en1 default
sudo route add default 192.168.82.1
```

Notice the I flag, for -ifscope, on the physical default route.

route change does not seem to work reliably.

* fix docker tag for controllers (#2066)

* Update build.sh (#2068)

fix mkwork compilation errors

* Fix network DNS on macOS

It stopped working for ipv4 only networks in Monterey.
See #1696

We add some config like so to System Configuration

```
scutil
show State:/Network/Service/9bee8941b5xxxxxx/IPv4
<dictionary> {
  Addresses : <array> {
    0 : 10.2.1.36
  }
  InterfaceName : feth4823
  Router : 10.2.1.36
  ServerAddress : 127.0.0.1
}

```

* Add search domain to macos dns configuration

Stumbled upon this while debugging something else.
If we add search domain to our system configuration for
network DNS, then search domains work:

```
ping server1                                                                                                                                                                                    ~
PING server1.my.domain (10.123.3.1): 56 data bytes
64 bytes from 10.123.3.1
```

* Fix reporting of secondaryPort and tertiaryPort See: #2039

* Fix typos (#2075)

* Disable executable stacks on assembly objects (#2071)

Add `--noexecstack` to the assembler flags so the resulting binary
will link with a non-executable stack.

Fixes zerotier/ZeroTierOne#1179

Co-authored-by: Joseph Henry <joseph.henry@zerotier.com>

* Test that starting zerotier before internet works

* Don't skip hellos when there are no paths available

working on #2082

* Update validate-1m-linux.sh

* Save zt node log files on abort

* Separate test and summary step in validator script

* Don't apply default route until zerotier is "online"

I was running into issues with restarting the zerotier service while
"full tunnel" mode is enabled.
When zerotier first boots, it gets network state from the cache
on disk. So it immediately applies all the routes it knew about
before it shutdown.
The network config may have change in this time.
If it has, then your default route is via a route
you are blocked from talking on. So you  can't get the current
network config, so your internet does not work.

Other options include
- don't use cached network state on boot
- find a better criteria than "online"

* Fix node time-to-online counter in validator script

* Export variables so that they are accessible by exit function

* Fix PortMapper issue on ZeroTier startup

See issue #2082

We use a call to libnatpmp::ininatpp to make sure the computer
has working network sockets before we go into the main
nat-pmp/upnp logic.

With basic exponenetial delay up to 30 seconds.

* testing

* Comment out PortMapper debug

this got left turned on in a confusing merge previously

* fix macos default route again

see commit fb6af1971 * Fix network DNS on macOS
adding that stuff to System Config causes this extra route to be added
which breaks ipv4 default route.
We figured out a weird System Coniguration setting
that works.

--- old
couldn't figure out how to fix it in SystemConfiguration
so here we are# Please enter the commit message for your changes. Lines starting

We also moved the dns setter to before the syncIps stuff
to help with a race condition. It didn't always work when
you re-joined a network with default route enabled.

* Catch all conditions in switch statement, remove trailing whitespaces

* Add setmtu command, fix bond lifetime issue

* Basic cleanups

* Check if null is passed to VirtualNetworkConfig.equals and name fixes

* ANDROID-96: Simplify and use return code from node_init directly

* Windows arm64 (#2099)

* ARM64 changes for 1.12

* 1.12 Windows advanced installer updates and updates for ARM64

* 1.12.0

* Linux build fixes for old distros.

* release notes

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: travis laduke <travisladuke@gmail.com>
Co-authored-by: Grant Limberg <grant.limberg@zerotier.com>
Co-authored-by: Grant Limberg <glimberg@users.noreply.github.com>
Co-authored-by: Leonardo Amaral <leleobhz@users.noreply.github.com>
Co-authored-by: Brenton Bostick <bostick@gmail.com>
Co-authored-by: Sean OMeara <someara@users.noreply.github.com>
Co-authored-by: Joseph Henry <joseph-henry@users.noreply.github.com>
Co-authored-by: Roman Peshkichev <roman.peshkichev@gmail.com>
Co-authored-by: Joseph Henry <joseph.henry@zerotier.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Stavros Kois <47820033+stavros-k@users.noreply.github.com>
Co-authored-by: Jake Vis <jakevis@outlook.com>
Co-authored-by: Jörg Thalheim <joerg@thalheim.io>
Co-authored-by: lison <imlison@foxmail.com>
Co-authored-by: Kenny MacDermid <kenny@macdermid.ca>
2023-08-23 14:24:21 -04:00

1189 lines
34 KiB
C++

#include <array>
#include <cstring>
#include <map>
#include <memory>
#include <numeric>
#include <optional>
#if defined(PQXX_HAVE_SPAN) && __has_include(<span>)
# include <span>
#endif
#include <type_traits>
#include <variant>
#include <vector>
#include "pqxx/types.hxx"
#include "pqxx/util.hxx"
/* Internal helpers for string conversion, and conversion implementations.
*
* Do not include this header directly. The libpqxx headers do it for you.
*/
namespace pqxx::internal
{
/// Convert a number in [0, 9] to its ASCII digit.
inline constexpr char number_to_digit(int i) noexcept
{
return static_cast<char>(i + '0');
}
/// Compute numeric value of given textual digit (assuming that it is a digit).
constexpr int digit_to_number(char c) noexcept
{
return c - '0';
}
/// Summarize buffer overrun.
/** Don't worry about the exact parameter types: the sizes will be reasonably
* small, and nonnegative.
*/
std::string PQXX_LIBEXPORT
state_buffer_overrun(int have_bytes, int need_bytes);
template<typename HAVE, typename NEED>
inline std::string state_buffer_overrun(HAVE have_bytes, NEED need_bytes)
{
return state_buffer_overrun(
static_cast<int>(have_bytes), static_cast<int>(need_bytes));
}
/// Throw exception for attempt to convert null to given type.
[[noreturn]] PQXX_LIBEXPORT void
throw_null_conversion(std::string const &type);
/// Deliberately nonfunctional conversion traits for `char` types.
/** There are no string conversions for `char` and its signed and unsigned
* variants. Such a conversion would be dangerously ambiguous: should we treat
* it as text, or as a small integer? It'd be an open invitation for bugs.
*
* But the error message when you get this wrong is very cryptic. So, we
* derive dummy @ref string_traits implementations from this dummy type, and
* ensure that the compiler disallows their use. The compiler error message
* will at least contain a hint of the root of the problem.
*/
template<typename CHAR_TYPE> struct disallowed_ambiguous_char_conversion
{
static char *into_buf(char *, char *, CHAR_TYPE) = delete;
static constexpr zview
to_buf(char *, char *, CHAR_TYPE const &) noexcept = delete;
static constexpr std::size_t
size_buffer(CHAR_TYPE const &) noexcept = delete;
static CHAR_TYPE from_string(std::string_view) = delete;
};
template<typename T> PQXX_LIBEXPORT extern std::string to_string_float(T);
/// Generic implementation for into_buf, on top of to_buf.
template<typename T>
inline char *generic_into_buf(char *begin, char *end, T const &value)
{
zview const text{string_traits<T>::to_buf(begin, end, value)};
auto const space{end - begin};
// Include the trailing zero.
auto const len = std::size(text) + 1;
if (internal::cmp_greater(len, space))
throw conversion_overrun{
"Not enough buffer space to insert " + type_name<T> + ". " +
state_buffer_overrun(space, len)};
std::memmove(begin, text.data(), len);
return begin + len;
}
/// String traits for builtin integral types (though not bool).
template<typename T> struct integral_traits
{
static PQXX_LIBEXPORT T from_string(std::string_view text);
static PQXX_LIBEXPORT zview to_buf(char *begin, char *end, T const &value);
static PQXX_LIBEXPORT char *into_buf(char *begin, char *end, T const &value);
static constexpr std::size_t size_buffer(T const &) noexcept
{
/** Includes a sign if needed; the number of base-10 digits which the type
* can reliably represent; the one extra base-10 digit which the type can
* only partially represent; and the terminating zero.
*/
return std::is_signed_v<T> + std::numeric_limits<T>::digits10 + 1 + 1;
}
};
/// String traits for builtin floating-point types.
template<typename T> struct float_traits
{
static PQXX_LIBEXPORT T from_string(std::string_view text);
static PQXX_LIBEXPORT zview to_buf(char *begin, char *end, T const &value);
static PQXX_LIBEXPORT char *into_buf(char *begin, char *end, T const &value);
// Return a nonnegative integral value's number of decimal digits.
static constexpr std::size_t digits10(std::size_t value) noexcept
{
if (value < 10)
return 1;
else
return 1 + digits10(value / 10);
}
static constexpr std::size_t size_buffer(T const &) noexcept
{
using lims = std::numeric_limits<T>;
// See #328 for a detailed discussion on the maximum number of digits.
//
// In a nutshell: for the big cases, the scientific notation is always
// the shortest one, and therefore the one that to_chars will pick.
//
// So... How long can the scientific notation get? 1 (for sign) + 1 (for
// decimal point) + 1 (for 'e') + 1 (for exponent sign) + max_digits10 +
// max number of digits in the exponent + 1 (terminating zero).
//
// What's the max number of digits in the exponent? It's the max number of
// digits out of the most negative exponent and the most positive one.
//
// The longest positive exponent is easy: 1 + ceil(log10(max_exponent10)).
// (The extra 1 is because 10^n takes up 1 + n digits, not n.)
//
// The longest negative exponent is a bit harder: min_exponent10 gives us
// the smallest power of 10 which a normalised version of T can represent.
// But the smallest denormalised power of 10 that T can represent is
// another max_digits10 powers of 10 below that.
// needs a minus sign.
//
// All this stuff messes with my head a bit because it's on the order of
// log10(log10(n)). It's easy to get the number of logs wrong.
auto const max_pos_exp{digits10(lims::max_exponent10)};
// Really want std::abs(lims::min_exponent10), but MSVC 2017 apparently has
// problems with std::abs. So we use -lims::min_exponent10 instead.
auto const max_neg_exp{
digits10(lims::max_digits10 - lims::min_exponent10)};
return 1 + // Sign.
1 + // Decimal point.
std::numeric_limits<T>::max_digits10 + // Mantissa digits.
1 + // Exponent "e".
1 + // Exponent sign.
// Spell this weirdly to stop Windows compilers from reading this as
// a call to their "max" macro when NOMINMAX is not defined.
(std::max)(max_pos_exp, max_neg_exp) + // Exponent digits.
1; // Terminating zero.
}
};
} // namespace pqxx::internal
namespace pqxx
{
/// The built-in arithmetic types do not have inherent null values.
template<typename T>
struct nullness<T, std::enable_if_t<std::is_arithmetic_v<T>>> : no_null<T>
{};
template<> struct string_traits<short> : internal::integral_traits<short>
{};
template<> inline constexpr bool is_unquoted_safe<short>{true};
template<>
struct string_traits<unsigned short>
: internal::integral_traits<unsigned short>
{};
template<> inline constexpr bool is_unquoted_safe<unsigned short>{true};
template<> struct string_traits<int> : internal::integral_traits<int>
{};
template<> inline constexpr bool is_unquoted_safe<int>{true};
template<> struct string_traits<unsigned> : internal::integral_traits<unsigned>
{};
template<> inline constexpr bool is_unquoted_safe<unsigned>{true};
template<> struct string_traits<long> : internal::integral_traits<long>
{};
template<> inline constexpr bool is_unquoted_safe<long>{true};
template<>
struct string_traits<unsigned long> : internal::integral_traits<unsigned long>
{};
template<> inline constexpr bool is_unquoted_safe<unsigned long>{true};
template<>
struct string_traits<long long> : internal::integral_traits<long long>
{};
template<> inline constexpr bool is_unquoted_safe<long long>{true};
template<>
struct string_traits<unsigned long long>
: internal::integral_traits<unsigned long long>
{};
template<> inline constexpr bool is_unquoted_safe<unsigned long long>{true};
template<> struct string_traits<float> : internal::float_traits<float>
{};
template<> inline constexpr bool is_unquoted_safe<float>{true};
template<> struct string_traits<double> : internal::float_traits<double>
{};
template<> inline constexpr bool is_unquoted_safe<double>{true};
template<>
struct string_traits<long double> : internal::float_traits<long double>
{};
template<> inline constexpr bool is_unquoted_safe<long double>{true};
template<> struct string_traits<bool>
{
static PQXX_LIBEXPORT bool from_string(std::string_view text);
static constexpr zview to_buf(char *, char *, bool const &value) noexcept
{
return value ? "true"_zv : "false"_zv;
}
static char *into_buf(char *begin, char *end, bool const &value)
{
return pqxx::internal::generic_into_buf(begin, end, value);
}
static constexpr std::size_t size_buffer(bool const &) noexcept { return 6; }
};
/// We don't support conversion to/from `char` types.
/** Why are these disallowed? Because they are ambiguous. It's not inherently
* clear whether we should treat values of these types as text or as small
* integers. Either choice would lead to bugs.
*/
template<>
struct string_traits<char>
: internal::disallowed_ambiguous_char_conversion<char>
{};
/// We don't support conversion to/from `char` types.
/** Why are these disallowed? Because they are ambiguous. It's not inherently
* clear whether we should treat values of these types as text or as small
* integers. Either choice would lead to bugs.
*/
template<>
struct string_traits<signed char>
: internal::disallowed_ambiguous_char_conversion<signed char>
{};
/// We don't support conversion to/from `char` types.
/** Why are these disallowed? Because they are ambiguous. It's not inherently
* clear whether we should treat values of these types as text or as small
* integers. Either choice would lead to bugs.
*/
template<>
struct string_traits<unsigned char>
: internal::disallowed_ambiguous_char_conversion<unsigned char>
{};
template<> inline constexpr bool is_unquoted_safe<bool>{true};
template<typename T> struct nullness<std::optional<T>>
{
static constexpr bool has_null = true;
/// Technically, you could have an optional of an always-null type.
static constexpr bool always_null = nullness<T>::always_null;
static constexpr bool is_null(std::optional<T> const &v) noexcept
{
return ((not v.has_value()) or pqxx::is_null(*v));
}
static constexpr std::optional<T> null() { return {}; }
};
template<typename T>
inline constexpr format param_format(std::optional<T> const &value)
{
return param_format(*value);
}
template<typename T> struct string_traits<std::optional<T>>
{
static char *into_buf(char *begin, char *end, std::optional<T> const &value)
{
return string_traits<T>::into_buf(begin, end, *value);
}
static zview to_buf(char *begin, char *end, std::optional<T> const &value)
{
if (value.has_value())
return string_traits<T>::to_buf(begin, end, *value);
else
return {};
}
static std::optional<T> from_string(std::string_view text)
{
return std::optional<T>{
std::in_place, string_traits<T>::from_string(text)};
}
static std::size_t size_buffer(std::optional<T> const &value) noexcept
{
return pqxx::size_buffer(value.value());
}
};
template<typename T>
inline constexpr bool is_unquoted_safe<std::optional<T>>{is_unquoted_safe<T>};
template<typename... T> struct nullness<std::variant<T...>>
{
static constexpr bool has_null = (nullness<T>::has_null or ...);
static constexpr bool always_null = (nullness<T>::always_null and ...);
static constexpr bool is_null(std::variant<T...> const &value) noexcept
{
return std::visit(
[](auto const &i) noexcept {
return nullness<strip_t<decltype(i)>>::is_null(i);
},
value);
}
// We don't support `null()` for `std::variant`.
/** It would be technically possible to have a `null` in the case where just
* one of the types has a null, but it gets complicated and arbitrary.
*/
static constexpr std::variant<T...> null() = delete;
};
template<typename... T> struct string_traits<std::variant<T...>>
{
static char *
into_buf(char *begin, char *end, std::variant<T...> const &value)
{
return std::visit(
[begin, end](auto const &i) {
return string_traits<strip_t<decltype(i)>>::into_buf(begin, end, i);
},
value);
}
static zview to_buf(char *begin, char *end, std::variant<T...> const &value)
{
return std::visit(
[begin, end](auto const &i) {
return string_traits<strip_t<decltype(i)>>::to_buf(begin, end, i);
},
value);
}
static std::size_t size_buffer(std::variant<T...> const &value) noexcept
{
return std::visit(
[](auto const &i) noexcept { return pqxx::size_buffer(i); }, value);
}
/** There's no from_string for std::variant. We could have one with a rule
* like "pick the first type which fits the value," but we'd have to look
* into how natural that API feels to users.
*/
static std::variant<T...> from_string(std::string_view) = delete;
};
template<typename... Args>
inline constexpr format param_format(std::variant<Args...> const &value)
{
return std::visit([](auto &v) { return param_format(v); }, value);
}
template<typename... T>
inline constexpr bool is_unquoted_safe<std::variant<T...>>{
(is_unquoted_safe<T> and ...)};
template<typename T> inline T from_string(std::stringstream const &text)
{
return from_string<T>(text.str());
}
template<> struct string_traits<std::nullptr_t>
{
static char *into_buf(char *, char *, std::nullptr_t) = delete;
static constexpr zview
to_buf(char *, char *, std::nullptr_t const &) noexcept
{
return {};
}
static constexpr std::size_t size_buffer(std::nullptr_t = nullptr) noexcept
{
return 0;
}
static std::nullptr_t from_string(std::string_view) = delete;
};
template<> struct string_traits<std::nullopt_t>
{
static char *into_buf(char *, char *, std::nullopt_t) = delete;
static constexpr zview
to_buf(char *, char *, std::nullopt_t const &) noexcept
{
return {};
}
static constexpr std::size_t size_buffer(std::nullopt_t) noexcept
{
return 0;
}
static std::nullopt_t from_string(std::string_view) = delete;
};
template<> struct string_traits<std::monostate>
{
static char *into_buf(char *, char *, std::monostate) = delete;
static constexpr zview
to_buf(char *, char *, std::monostate const &) noexcept
{
return {};
}
static constexpr std::size_t size_buffer(std::monostate) noexcept
{
return 0;
}
static std::monostate from_string(std::string_view) = delete;
};
template<> inline constexpr bool is_unquoted_safe<std::nullptr_t>{true};
template<> struct nullness<char const *>
{
static constexpr bool has_null = true;
static constexpr bool always_null = false;
static constexpr bool is_null(char const *t) noexcept
{
return t == nullptr;
}
static constexpr char const *null() noexcept { return nullptr; }
};
/// String traits for C-style string ("pointer to char const").
template<> struct string_traits<char const *>
{
static char const *from_string(std::string_view text) { return text.data(); }
static zview to_buf(char *begin, char *end, char const *const &value)
{
return generic_to_buf(begin, end, value);
}
static char *into_buf(char *begin, char *end, char const *const &value)
{
auto const space{end - begin};
// Count the trailing zero, even though std::strlen() and friends don't.
auto const len{std::strlen(value) + 1};
if (space < ptrdiff_t(len))
throw conversion_overrun{
"Could not copy string: buffer too small. " +
pqxx::internal::state_buffer_overrun(space, len)};
std::memmove(begin, value, len);
return begin + len;
}
static std::size_t size_buffer(char const *const &value) noexcept
{
return std::strlen(value) + 1;
}
};
template<> struct nullness<char *>
{
static constexpr bool has_null = true;
static constexpr bool always_null = false;
static constexpr bool is_null(char const *t) noexcept
{
return t == nullptr;
}
static constexpr char const *null() { return nullptr; }
};
/// String traits for non-const C-style string ("pointer to char").
template<> struct string_traits<char *>
{
static char *into_buf(char *begin, char *end, char *const &value)
{
return string_traits<char const *>::into_buf(begin, end, value);
}
static zview to_buf(char *begin, char *end, char *const &value)
{
return string_traits<char const *>::to_buf(begin, end, value);
}
static std::size_t size_buffer(char *const &value) noexcept
{
return string_traits<char const *>::size_buffer(value);
}
/// Don't allow conversion to this type since it breaks const-safety.
static char *from_string(std::string_view) = delete;
};
template<std::size_t N> struct nullness<char[N]> : no_null<char[N]>
{};
/// String traits for C-style string constant ("array of char").
/** @warning This assumes that every array-of-char is a C-style string literal.
* So, it must include a trailing zero. and it must have static duration.
*/
template<std::size_t N> struct string_traits<char[N]>
{
static constexpr zview
to_buf(char *, char *, char const (&value)[N]) noexcept
{
return zview{value, N - 1};
}
static char *into_buf(char *begin, char *end, char const (&value)[N])
{
if (internal::cmp_less(end - begin, size_buffer(value)))
throw conversion_overrun{
"Could not convert char[] to string: too long for buffer."};
std::memcpy(begin, value, N);
return begin + N;
}
static constexpr std::size_t size_buffer(char const (&)[N]) noexcept
{
return N;
}
/// Don't allow conversion to this type.
static void from_string(std::string_view) = delete;
};
template<> struct nullness<std::string> : no_null<std::string>
{};
template<> struct string_traits<std::string>
{
static std::string from_string(std::string_view text)
{
return std::string{text};
}
static char *into_buf(char *begin, char *end, std::string const &value)
{
if (internal::cmp_greater_equal(std::size(value), end - begin))
throw conversion_overrun{
"Could not convert string to string: too long for buffer."};
// Include the trailing zero.
value.copy(begin, std::size(value));
begin[std::size(value)] = '\0';
return begin + std::size(value) + 1;
}
static zview to_buf(char *begin, char *end, std::string const &value)
{
return generic_to_buf(begin, end, value);
}
static std::size_t size_buffer(std::string const &value) noexcept
{
return std::size(value) + 1;
}
};
/// There's no real null for `std::string_view`.
/** I'm not sure how clear-cut this is: a `string_view` may have a null
* data pointer, which is analogous to a null `char` pointer.
*/
template<> struct nullness<std::string_view> : no_null<std::string_view>
{};
/// String traits for `string_view`.
template<> struct string_traits<std::string_view>
{
static constexpr std::size_t
size_buffer(std::string_view const &value) noexcept
{
return std::size(value) + 1;
}
static char *into_buf(char *begin, char *end, std::string_view const &value)
{
if (internal::cmp_greater_equal(std::size(value), end - begin))
throw conversion_overrun{
"Could not store string_view: too long for buffer."};
value.copy(begin, std::size(value));
begin[std::size(value)] = '\0';
return begin + std::size(value) + 1;
}
/// Don't convert to this type; it has nowhere to store its contents.
static std::string_view from_string(std::string_view) = delete;
};
template<> struct nullness<zview> : no_null<zview>
{};
/// String traits for `zview`.
template<> struct string_traits<zview>
{
static constexpr std::size_t
size_buffer(std::string_view const &value) noexcept
{
return std::size(value) + 1;
}
static char *into_buf(char *begin, char *end, zview const &value)
{
auto const size{std::size(value)};
if (internal::cmp_less_equal(end - begin, std::size(value)))
throw conversion_overrun{"Not enough buffer space to store this zview."};
value.copy(begin, size);
begin[size] = '\0';
return begin + size + 1;
}
static std::string_view to_buf(char *begin, char *end, zview const &value)
{
return {into_buf(begin, end, value), std::size(value)};
}
/// Don't convert to this type; it has nowhere to store its contents.
static zview from_string(std::string_view) = delete;
};
template<> struct nullness<std::stringstream> : no_null<std::stringstream>
{};
template<> struct string_traits<std::stringstream>
{
static std::size_t size_buffer(std::stringstream const &) = delete;
static std::stringstream from_string(std::string_view text)
{
std::stringstream stream;
stream.write(text.data(), std::streamsize(std::size(text)));
return stream;
}
static char *into_buf(char *, char *, std::stringstream const &) = delete;
static std::string_view
to_buf(char *, char *, std::stringstream const &) = delete;
};
template<> struct nullness<std::nullptr_t>
{
static constexpr bool has_null = true;
static constexpr bool always_null = true;
static constexpr bool is_null(std::nullptr_t const &) noexcept
{
return true;
}
static constexpr std::nullptr_t null() noexcept { return nullptr; }
};
template<> struct nullness<std::nullopt_t>
{
static constexpr bool has_null = true;
static constexpr bool always_null = true;
static constexpr bool is_null(std::nullopt_t const &) noexcept
{
return true;
}
static constexpr std::nullopt_t null() noexcept { return std::nullopt; }
};
template<> struct nullness<std::monostate>
{
static constexpr bool has_null = true;
static constexpr bool always_null = true;
static constexpr bool is_null(std::monostate const &) noexcept
{
return true;
}
static constexpr std::monostate null() noexcept { return {}; }
};
template<typename T> struct nullness<std::unique_ptr<T>>
{
static constexpr bool has_null = true;
static constexpr bool always_null = false;
static constexpr bool is_null(std::unique_ptr<T> const &t) noexcept
{
return not t or pqxx::is_null(*t);
}
static constexpr std::unique_ptr<T> null() { return {}; }
};
template<typename T, typename... Args>
struct string_traits<std::unique_ptr<T, Args...>>
{
static std::unique_ptr<T> from_string(std::string_view text)
{
return std::make_unique<T>(string_traits<T>::from_string(text));
}
static char *
into_buf(char *begin, char *end, std::unique_ptr<T, Args...> const &value)
{
return string_traits<T>::into_buf(begin, end, *value);
}
static zview
to_buf(char *begin, char *end, std::unique_ptr<T, Args...> const &value)
{
if (value)
return string_traits<T>::to_buf(begin, end, *value);
else
return {};
}
static std::size_t
size_buffer(std::unique_ptr<T, Args...> const &value) noexcept
{
return pqxx::size_buffer(*value.get());
}
};
template<typename T, typename... Args>
inline format param_format(std::unique_ptr<T, Args...> const &value)
{
return param_format(*value);
}
template<typename T, typename... Args>
inline constexpr bool is_unquoted_safe<std::unique_ptr<T, Args...>>{
is_unquoted_safe<T>};
template<typename T> struct nullness<std::shared_ptr<T>>
{
static constexpr bool has_null = true;
static constexpr bool always_null = false;
static constexpr bool is_null(std::shared_ptr<T> const &t) noexcept
{
return not t or pqxx::is_null(*t);
}
static constexpr std::shared_ptr<T> null() { return {}; }
};
template<typename T> struct string_traits<std::shared_ptr<T>>
{
static std::shared_ptr<T> from_string(std::string_view text)
{
return std::make_shared<T>(string_traits<T>::from_string(text));
}
static zview to_buf(char *begin, char *end, std::shared_ptr<T> const &value)
{
return string_traits<T>::to_buf(begin, end, *value);
}
static char *
into_buf(char *begin, char *end, std::shared_ptr<T> const &value)
{
return string_traits<T>::into_buf(begin, end, *value);
}
static std::size_t size_buffer(std::shared_ptr<T> const &value) noexcept
{
return pqxx::size_buffer(*value);
}
};
template<typename T> format param_format(std::shared_ptr<T> const &value)
{
return param_format(*value);
}
template<typename T>
inline constexpr bool is_unquoted_safe<std::shared_ptr<T>>{
is_unquoted_safe<T>};
template<>
struct nullness<std::basic_string<std::byte>>
: no_null<std::basic_string<std::byte>>
{};
#if defined(PQXX_HAVE_CONCEPTS)
template<binary DATA> struct nullness<DATA> : no_null<DATA>
{};
template<binary DATA> inline constexpr format param_format(DATA const &)
{
return format::binary;
}
template<binary DATA> struct string_traits<DATA>
{
static std::size_t size_buffer(DATA const &value) noexcept
{
return internal::size_esc_bin(std::size(value));
}
static zview to_buf(char *begin, char *end, DATA const &value)
{
return generic_to_buf(begin, end, value);
}
static char *into_buf(char *begin, char *end, DATA const &value)
{
auto const budget{size_buffer(value)};
if (internal::cmp_less(end - begin, budget))
throw conversion_overrun{
"Not enough buffer space to escape binary data."};
internal::esc_bin(value, begin);
return begin + budget;
}
static DATA from_string(std::string_view text)
{
auto const size{pqxx::internal::size_unesc_bin(std::size(text))};
std::basic_string<std::byte> buf;
buf.resize(size);
pqxx::internal::unesc_bin(text, reinterpret_cast<std::byte *>(buf.data()));
return buf;
}
};
#endif // PQXX_HAVE_CONCEPTS
template<> struct string_traits<std::basic_string<std::byte>>
{
static std::size_t
size_buffer(std::basic_string<std::byte> const &value) noexcept
{
return internal::size_esc_bin(std::size(value));
}
static zview
to_buf(char *begin, char *end, std::basic_string<std::byte> const &value)
{
return generic_to_buf(begin, end, value);
}
static char *
into_buf(char *begin, char *end, std::basic_string<std::byte> const &value)
{
auto const budget{size_buffer(value)};
if (internal::cmp_less(end - begin, budget))
throw conversion_overrun{
"Not enough buffer space to escape binary data."};
internal::esc_bin(value, begin);
return begin + budget;
}
static std::basic_string<std::byte> from_string(std::string_view text)
{
auto const size{pqxx::internal::size_unesc_bin(std::size(text))};
std::basic_string<std::byte> buf;
buf.resize(size);
pqxx::internal::unesc_bin(text, reinterpret_cast<std::byte *>(buf.data()));
return buf;
}
};
template<>
inline constexpr format param_format(std::basic_string<std::byte> const &)
{
return format::binary;
}
template<>
struct nullness<std::basic_string_view<std::byte>>
: no_null<std::basic_string_view<std::byte>>
{};
template<> struct string_traits<std::basic_string_view<std::byte>>
{
static std::size_t
size_buffer(std::basic_string_view<std::byte> const &value) noexcept
{
return internal::size_esc_bin(std::size(value));
}
static zview to_buf(
char *begin, char *end, std::basic_string_view<std::byte> const &value)
{
return generic_to_buf(begin, end, value);
}
static char *into_buf(
char *begin, char *end, std::basic_string_view<std::byte> const &value)
{
auto const budget{size_buffer(value)};
if (internal::cmp_less(end - begin, budget))
throw conversion_overrun{
"Not enough buffer space to escape binary data."};
internal::esc_bin(value, begin);
return begin + budget;
}
// There's no from_string, because there's nobody to hold the data.
};
template<>
inline constexpr format param_format(std::basic_string_view<std::byte> const &)
{
return format::binary;
}
} // namespace pqxx
namespace pqxx::internal
{
/// String traits for SQL arrays.
template<typename Container> struct array_string_traits
{
private:
using elt_type = strip_t<value_type<Container>>;
using elt_traits = string_traits<elt_type>;
static constexpr zview s_null{"NULL"};
public:
static zview to_buf(char *begin, char *end, Container const &value)
{
return generic_to_buf(begin, end, value);
}
static char *into_buf(char *begin, char *end, Container const &value)
{
std::size_t const budget{size_buffer(value)};
if (internal::cmp_less(end - begin, budget))
throw conversion_overrun{
"Not enough buffer space to convert array to string."};
char *here = begin;
*here++ = '{';
bool nonempty{false};
for (auto const &elt : value)
{
if (is_null(elt))
{
s_null.copy(here, std::size(s_null));
here += std::size(s_null);
}
else if constexpr (is_sql_array<elt_type>)
{
// Render nested array in-place. Then erase the trailing zero.
here = elt_traits::into_buf(here, end, elt) - 1;
}
else if constexpr (is_unquoted_safe<elt_type>)
{
// No need to quote or escape. Just convert the value straight into
// its place in the array, and "backspace" the trailing zero.
here = elt_traits::into_buf(here, end, elt) - 1;
}
else
{
*here++ = '"';
// Use the tail end of the destination buffer as an intermediate
// buffer.
auto const elt_budget{pqxx::size_buffer(elt)};
for (char const c : elt_traits::to_buf(end - elt_budget, end, elt))
{
if (c == '\\' or c == '"')
*here++ = '\\';
*here++ = c;
}
*here++ = '"';
}
*here++ = array_separator<elt_type>;
nonempty = true;
}
// Erase that last comma, if present.
if (nonempty)
here--;
*here++ = '}';
*here++ = '\0';
return here;
}
static std::size_t size_buffer(Container const &value) noexcept
{
if constexpr (is_unquoted_safe<elt_type>)
return 3 + std::accumulate(
std::begin(value), std::end(value), std::size_t{},
[](std::size_t acc, elt_type const &elt) {
return acc +
(pqxx::is_null(elt) ?
std::size(s_null) :
elt_traits::size_buffer(elt)) -
1;
});
else
return 3 + std::accumulate(
std::begin(value), std::end(value), std::size_t{},
[](std::size_t acc, elt_type const &elt) {
// Opening and closing quotes, plus worst-case escaping,
// but don't count the trailing zeroes.
std::size_t const elt_size{
pqxx::is_null(elt) ? std::size(s_null) :
elt_traits::size_buffer(elt) - 1};
return acc + 2 * elt_size + 2;
});
}
// We don't yet support parsing of array types using from_string. Doing so
// would require a reference to the connection.
};
} // namespace pqxx::internal
namespace pqxx
{
template<typename T, typename... Args>
struct nullness<std::vector<T, Args...>> : no_null<std::vector<T>>
{};
template<typename T, typename... Args>
struct string_traits<std::vector<T, Args...>>
: internal::array_string_traits<std::vector<T, Args...>>
{};
/// We don't know how to pass array params in binary format, so pass as text.
template<typename T, typename... Args>
inline constexpr format param_format(std::vector<T, Args...> const &)
{
return format::text;
}
/// A `std::vector<std::byte>` is a binary string. Other vectors are not.
template<typename... Args>
inline constexpr format param_format(std::vector<std::byte, Args...> const &)
{
return format::binary;
}
template<typename T> inline constexpr bool is_sql_array<std::vector<T>>{true};
template<typename T, std::size_t N>
struct nullness<std::array<T, N>> : no_null<std::array<T, N>>
{};
template<typename T, std::size_t N>
struct string_traits<std::array<T, N>>
: internal::array_string_traits<std::array<T, N>>
{};
/// We don't know how to pass array params in binary format, so pass as text.
template<typename T, typename... Args, Args... args>
inline constexpr format param_format(std::array<T, args...> const &)
{
return format::text;
}
/// An array of `std::byte` is a binary string.
template<typename... Args, Args... args>
inline constexpr format param_format(std::array<std::byte, args...> const &)
{
return format::binary;
}
template<typename T, std::size_t N>
inline constexpr bool is_sql_array<std::array<T, N>>{true};
} // namespace pqxx
namespace pqxx
{
template<typename T> inline std::string to_string(T const &value)
{
if (is_null(value))
throw conversion_error{
"Attempt to convert null " + type_name<T> + " to a string."};
std::string buf;
// We can't just reserve() space; modifying the terminating zero leads to
// undefined behaviour.
buf.resize(size_buffer(value));
auto const data{buf.data()};
auto const end{
string_traits<T>::into_buf(data, data + std::size(buf), value)};
buf.resize(static_cast<std::size_t>(end - data - 1));
return buf;
}
template<> inline std::string to_string(float const &value)
{
return internal::to_string_float(value);
}
template<> inline std::string to_string(double const &value)
{
return internal::to_string_float(value);
}
template<> inline std::string to_string(long double const &value)
{
return internal::to_string_float(value);
}
template<> inline std::string to_string(std::stringstream const &value)
{
return value.str();
}
template<typename T> inline void into_string(T const &value, std::string &out)
{
if (is_null(value))
throw conversion_error{
"Attempt to convert null " + type_name<T> + " to a string."};
// We can't just reserve() data; modifying the terminating zero leads to
// undefined behaviour.
out.resize(size_buffer(value) + 1);
auto const data{out.data()};
auto const end{
string_traits<T>::into_buf(data, data + std::size(out), value)};
out.resize(static_cast<std::size_t>(end - data - 1));
}
} // namespace pqxx