1 #include <cmath>
2 
3 #include <pqxx/transaction>
4 
5 #include "../test_helpers.hxx"
6 
7 namespace
8 {
9 /// Test conversions for some floating-point type.
infinity_test()10 template<typename T> void infinity_test()
11 {
12   T inf{std::numeric_limits<T>::infinity()};
13   std::string inf_string;
14   T back_conversion;
15 
16   inf_string = pqxx::to_string(inf);
17   pqxx::from_string(inf_string, back_conversion);
18   PQXX_CHECK_LESS(
19     T(999999999), back_conversion,
20     "Infinity doesn't convert back to something huge.");
21 
22   inf_string = pqxx::to_string(-inf);
23   pqxx::from_string(inf_string, back_conversion);
24   PQXX_CHECK_LESS(
25     back_conversion, -T(999999999), "Negative infinity is broken");
26 }
27 
test_infinities()28 void test_infinities()
29 {
30   infinity_test<float>();
31   infinity_test<double>();
32   infinity_test<long double>();
33 }
34 
35 
36 /// Reproduce bug #262: repeated float conversions break without charconv.
bug_262()37 template<typename T> void bug_262()
38 {
39   pqxx::connection conn;
40   conn.prepare("stmt", "select cast($1 as float)");
41   pqxx::work tr{conn};
42 
43   // We must use the same float type both for passing the value to the
44   // statement and for retrieving result of the statement execution.  This is
45   // due to an internal stringstream being instantiated as a a parameterized
46   // thread-local singleton.  So, there are separate stream<float>,
47   // stream<double>, stream<long double>, but every such instance is a
48   // singleton. We should use only one of them for this test.
49 
50   pqxx::row row;
51 
52   // Nothing bad here, select a float value.
53   // The stream is clear, so just fill it with the value and extract str().
54   row = tr.exec1("SELECT 1.0");
55 
56   // This works properly, but as we parse the value from the stream, the
57   // seeking cursor moves towards the EOF. When the inevitable EOF happens
58   // 'eof' flag is set in the stream and 'good' flag is unset.
59   row[0].as<T>();
60 
61   // The second try. Select a float value again. The stream is not clean, so
62   // we need to put an empty string into its buffer {stream.str("");}. This
63   // resets the seeking cursor to 0. Then we will put the value using
64   // operator<<().
65   // ...
66   // ...
67   // OOPS. stream.str("") does not reset 'eof' flag and 'good' flag! We are
68   // trying to read from EOF! This is no good.
69   // Throws on unpatched pqxx v6.4.5
70   row = tr.exec1("SELECT 2.0");
71 
72   // We won't get here without patch. The following statements are just for
73   // demonstration of how are intended to work. If we
74   // simply just reset the stream flags properly, this would work fine.
75   // The most obvious patch is just explicitly stream.seekg(0).
76   row[0].as<T>();
77   row = tr.exec1("SELECT 3.0");
78   row[0].as<T>();
79 }
80 
81 
82 /// Test for bug #262.
test_bug_262()83 void test_bug_262()
84 {
85   bug_262<float>();
86   bug_262<double>();
87   bug_262<long double>();
88 }
89 
90 
91 /// Test conversion of malformed floating-point values.
test_bad_float()92 void test_bad_float()
93 {
94   float x [[maybe_unused]];
95   PQXX_CHECK_THROWS(
96     x = pqxx::from_string<float>(""), pqxx::conversion_error,
97     "Conversion of empty string to float was not caught.");
98 
99   PQXX_CHECK_THROWS(
100     x = pqxx::from_string<float>("Infancy"), pqxx::conversion_error,
101     "Misleading infinity was not caught.");
102   PQXX_CHECK_THROWS(
103     x = pqxx::from_string<float>("-Infighting"), pqxx::conversion_error,
104     "Misleading negative infinity was not caught.");
105 
106   PQXX_CHECK_THROWS(
107     x = pqxx::from_string<float>("Nanny"), pqxx::conversion_error,
108     "Conversion of misleading NaN was not caught.");
109 }
110 
111 
test_float_length(T value)112 template<typename T> void test_float_length(T value)
113 {
114   auto const text{pqxx::to_string(value)};
115   PQXX_CHECK_GREATER_EQUAL(
116     pqxx::size_buffer(value), std::size(text) + 1,
117     "Not enough buffer space for " + text + ".");
118 }
119 
120 
121 /// Test conversion of long float values to strings.
test_long_float()122 void test_long_float()
123 {
124   test_float_length(0.1f);
125   test_float_length(0.1);
126 
127   test_float_length(std::numeric_limits<float>::denorm_min());
128   test_float_length(-std::numeric_limits<float>::denorm_min());
129   test_float_length(std::numeric_limits<float>::min());
130   test_float_length(-std::numeric_limits<float>::min());
131   test_float_length(std::numeric_limits<float>::max());
132   test_float_length(-std::numeric_limits<float>::max());
133   test_float_length(-std::nextafter(1.0f, 2.0f));
134 
135   test_float_length(std::numeric_limits<double>::denorm_min());
136   test_float_length(-std::numeric_limits<double>::denorm_min());
137   test_float_length(std::numeric_limits<double>::min());
138   test_float_length(-std::numeric_limits<double>::min());
139   test_float_length(std::numeric_limits<double>::max());
140   test_float_length(-std::numeric_limits<double>::max());
141   test_float_length(-std::nextafter(1.0, 2.0));
142 
143   test_float_length(std::numeric_limits<long double>::denorm_min());
144   test_float_length(-std::numeric_limits<long double>::denorm_min());
145   test_float_length(std::numeric_limits<long double>::min());
146   test_float_length(-std::numeric_limits<long double>::min());
147   test_float_length(std::numeric_limits<long double>::max());
148   test_float_length(-std::numeric_limits<long double>::max());
149   test_float_length(-std::nextafter(1.0L, 2.0L));
150 
151   // Ahem.  I'm not proud of this.  We really can't assume much about the
152   // floating-point types, but I'd really like to try a few things to see that
153   // buffer sizes are in the right ballpark.  So, if "double" is at least 64
154   // bits, check for some examples of long conversions.
155   if constexpr (sizeof(double) >= 8)
156   {
157     auto constexpr awkward{-2.2250738585072014e-308};
158     auto const text{pqxx::to_string(awkward)};
159     PQXX_CHECK_LESS_EQUAL(
160       std::size(text), 25u, text + " converted to too long a string.");
161   }
162   if constexpr (sizeof(double) <= 8)
163   {
164     auto const text{pqxx::to_string(0.99)};
165     PQXX_CHECK_LESS_EQUAL(
166       pqxx::size_buffer(0.99), 25u, text + " converted to too long a string.");
167   }
168 }
169 
170 
171 PQXX_REGISTER_TEST(test_infinities);
172 PQXX_REGISTER_TEST(test_bug_262);
173 PQXX_REGISTER_TEST(test_bad_float);
174 PQXX_REGISTER_TEST(test_long_float);
175 } // namespace
176