test_stream_from.cxx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  1. #include <pqxx/stream_from>
  2. #include <pqxx/transaction>
  3. #include "../test_helpers.hxx"
  4. #include "../test_types.hxx"
  5. #include <cstring>
  6. #include <iostream>
  7. #include <set>
  8. #include <string>
  9. #include <tuple>
  10. #include <vector>
  11. #include <optional>
  12. namespace
  13. {
  14. void test_nonoptionals(pqxx::connection &connection)
  15. {
  16. pqxx::work tx{connection};
  17. auto extractor{pqxx::stream_from::query(
  18. tx, "SELECT * FROM stream_from_test ORDER BY number0")};
  19. PQXX_CHECK(extractor, "stream_from failed to initialize.");
  20. std::tuple<int, std::string, int, ipv4, std::string, bytea> got_tuple;
  21. try
  22. {
  23. // We can't read the "910" row -- it contains nulls, which our tuple does
  24. // not accept.
  25. extractor >> got_tuple;
  26. PQXX_CHECK_NOTREACHED(
  27. "Failed to fail to stream null values into null-less fields.");
  28. }
  29. catch (pqxx::conversion_error const &e)
  30. {
  31. std::string const what{e.what()};
  32. if (what.find("null") == std::string::npos)
  33. throw;
  34. pqxx::test::expected_exception(
  35. "Could not stream nulls into null-less fields: " + what);
  36. }
  37. // The stream is still good though.
  38. // The second tuple is fine.
  39. extractor >> got_tuple;
  40. PQXX_CHECK(extractor, "Stream ended prematurely.");
  41. PQXX_CHECK_EQUAL(std::get<0>(got_tuple), 1234, "Bad value.");
  42. // Don't know much about the timestamp, but let's assume it starts with a
  43. // year in the second millennium.
  44. PQXX_CHECK(
  45. std::get<1>(got_tuple).at(0) == '2', "Bad value. Expected timestamp.");
  46. PQXX_CHECK_LESS(
  47. std::size(std::get<1>(got_tuple)), 40u, "Unexpected length.");
  48. PQXX_CHECK_GREATER(
  49. std::size(std::get<1>(got_tuple)), 20u, "Unexpected length.");
  50. PQXX_CHECK_EQUAL(std::get<2>(got_tuple), 4321, "Bad value.");
  51. PQXX_CHECK_EQUAL(std::get<3>(got_tuple), (ipv4{8, 8, 8, 8}), "Bad value.");
  52. PQXX_CHECK_EQUAL(std::get<4>(got_tuple), "hello\n \tworld", "Bad value.");
  53. PQXX_CHECK_EQUAL(
  54. std::get<5>(got_tuple), (bytea{'\x00', '\x01', '\x02'}), "Bad value.");
  55. // The third tuple contains some nulls. For what it's worth, when we *know*
  56. // that we're getting nulls, we can stream them into nullptr_t fields.
  57. std::tuple<
  58. int, std::string, std::nullptr_t, std::nullptr_t, std::string, bytea>
  59. tup_w_nulls;
  60. extractor >> tup_w_nulls;
  61. PQXX_CHECK(extractor, "Stream ended prematurely.");
  62. PQXX_CHECK_EQUAL(std::get<0>(tup_w_nulls), 5678, "Bad value.");
  63. PQXX_CHECK(std::get<2>(tup_w_nulls) == nullptr, "Bad null.");
  64. PQXX_CHECK(std::get<3>(tup_w_nulls) == nullptr, "Bad null.");
  65. // We're at the end of the stream.
  66. extractor >> tup_w_nulls;
  67. PQXX_CHECK(not extractor, "Stream did not end.");
  68. // Of course we can't stream a non-null value into a nullptr field.
  69. auto ex2{pqxx::stream_from::query(tx, "SELECT 1")};
  70. std::tuple<std::nullptr_t> null_tup;
  71. try
  72. {
  73. ex2 >> null_tup;
  74. PQXX_CHECK_NOTREACHED(
  75. "stream_from should have refused to convert non-null value to "
  76. "nullptr_t.");
  77. }
  78. catch (pqxx::conversion_error const &e)
  79. {
  80. std::string const what{e.what()};
  81. if (what.find("null") == std::string::npos)
  82. throw;
  83. pqxx::test::expected_exception(
  84. std::string{"Could not extract row: "} + what);
  85. }
  86. ex2 >> null_tup;
  87. PQXX_CHECK(not ex2, "Stream did not end.");
  88. PQXX_CHECK_SUCCEEDS(
  89. tx.exec1("SELECT 1"), "Could not use transaction after stream_from.");
  90. }
  91. void test_bad_tuples(pqxx::connection &conn)
  92. {
  93. pqxx::work tx{conn};
  94. auto extractor{pqxx::stream_from::table(tx, {"stream_from_test"})};
  95. PQXX_CHECK(extractor, "stream_from failed to initialize");
  96. std::tuple<int> got_tuple_too_short;
  97. try
  98. {
  99. extractor >> got_tuple_too_short;
  100. PQXX_CHECK_NOTREACHED("stream_from improperly read first row");
  101. }
  102. catch (pqxx::usage_error const &e)
  103. {
  104. std::string what{e.what()};
  105. if (
  106. what.find("1") == std::string::npos or
  107. what.find("6") == std::string::npos)
  108. throw;
  109. pqxx::test::expected_exception("Tuple is wrong size: " + what);
  110. }
  111. std::tuple<int, std::string, int, ipv4, std::string, bytea, std::string>
  112. got_tuple_too_long;
  113. try
  114. {
  115. extractor >> got_tuple_too_long;
  116. PQXX_CHECK_NOTREACHED("stream_from improperly read first row");
  117. }
  118. catch (pqxx::usage_error const &e)
  119. {
  120. std::string what{e.what()};
  121. if (
  122. what.find("6") == std::string::npos or
  123. what.find("7") == std::string::npos)
  124. throw;
  125. pqxx::test::expected_exception("Could not extract row: " + what);
  126. }
  127. extractor.complete();
  128. }
  129. #define ASSERT_FIELD_EQUAL(OPT, VAL) \
  130. PQXX_CHECK(static_cast<bool>(OPT), "unexpected null field"); \
  131. PQXX_CHECK_EQUAL(*OPT, VAL, "field value mismatch")
  132. #define ASSERT_FIELD_NULL(OPT) \
  133. PQXX_CHECK(not static_cast<bool>(OPT), "expected null field")
  134. template<template<typename...> class O>
  135. void test_optional(pqxx::connection &connection)
  136. {
  137. pqxx::work tx{connection};
  138. auto extractor{pqxx::stream_from::query(
  139. tx, "SELECT * FROM stream_from_test ORDER BY number0")};
  140. PQXX_CHECK(extractor, "stream_from failed to initialize");
  141. std::tuple<int, O<std::string>, O<int>, O<ipv4>, O<std::string>, O<bytea>>
  142. got_tuple;
  143. extractor >> got_tuple;
  144. PQXX_CHECK(extractor, "stream_from failed to read third row");
  145. PQXX_CHECK_EQUAL(std::get<0>(got_tuple), 910, "field value mismatch");
  146. ASSERT_FIELD_NULL(std::get<1>(got_tuple));
  147. ASSERT_FIELD_NULL(std::get<2>(got_tuple));
  148. ASSERT_FIELD_NULL(std::get<3>(got_tuple));
  149. ASSERT_FIELD_EQUAL(std::get<4>(got_tuple), "\\N");
  150. ASSERT_FIELD_EQUAL(std::get<5>(got_tuple), bytea{});
  151. extractor >> got_tuple;
  152. PQXX_CHECK(extractor, "stream_from failed to read first row.");
  153. PQXX_CHECK_EQUAL(std::get<0>(got_tuple), 1234, "Field value mismatch.");
  154. PQXX_CHECK(
  155. static_cast<bool>(std::get<1>(got_tuple)), "Unexpected null field.");
  156. // PQXX_CHECK_EQUAL(*std::get<1>(got_tuple), , "field value mismatch");
  157. ASSERT_FIELD_EQUAL(std::get<2>(got_tuple), 4321);
  158. ASSERT_FIELD_EQUAL(std::get<3>(got_tuple), (ipv4{8, 8, 8, 8}));
  159. ASSERT_FIELD_EQUAL(std::get<4>(got_tuple), "hello\n \tworld");
  160. ASSERT_FIELD_EQUAL(std::get<5>(got_tuple), (bytea{'\x00', '\x01', '\x02'}));
  161. extractor >> got_tuple;
  162. PQXX_CHECK(extractor, "stream_from failed to read second row");
  163. PQXX_CHECK_EQUAL(std::get<0>(got_tuple), 5678, "field value mismatch");
  164. ASSERT_FIELD_EQUAL(std::get<1>(got_tuple), "2018-11-17 21:23:00");
  165. ASSERT_FIELD_NULL(std::get<2>(got_tuple));
  166. ASSERT_FIELD_NULL(std::get<3>(got_tuple));
  167. ASSERT_FIELD_EQUAL(std::get<4>(got_tuple), "\u3053\u3093\u306b\u3061\u308f");
  168. ASSERT_FIELD_EQUAL(
  169. std::get<5>(got_tuple), (bytea{'f', 'o', 'o', ' ', 'b', 'a', 'r', '\0'}));
  170. extractor >> got_tuple;
  171. PQXX_CHECK(not extractor, "stream_from failed to detect end of stream");
  172. extractor.complete();
  173. }
  174. void test_stream_from()
  175. {
  176. pqxx::connection conn;
  177. pqxx::work tx{conn};
  178. tx.exec0(
  179. "CREATE TEMP TABLE stream_from_test ("
  180. "number0 INT NOT NULL,"
  181. "ts1 TIMESTAMP NULL,"
  182. "number2 INT NULL,"
  183. "addr3 INET NULL,"
  184. "txt4 TEXT NULL,"
  185. "bin5 BYTEA NOT NULL"
  186. ")");
  187. tx.exec_params(
  188. "INSERT INTO stream_from_test VALUES ($1,$2,$3,$4,$5,$6)", 910, nullptr,
  189. nullptr, nullptr, "\\N", bytea{});
  190. tx.exec_params(
  191. "INSERT INTO stream_from_test VALUES ($1,$2,$3,$4,$5,$6)", 1234, "now",
  192. 4321, ipv4{8, 8, 8, 8}, "hello\n \tworld", bytea{'\x00', '\x01', '\x02'});
  193. tx.exec_params(
  194. "INSERT INTO stream_from_test VALUES ($1,$2,$3,$4,$5,$6)", 5678,
  195. "2018-11-17 21:23:00", nullptr, nullptr, "\u3053\u3093\u306b\u3061\u308f",
  196. bytea{'f', 'o', 'o', ' ', 'b', 'a', 'r', '\0'});
  197. tx.commit();
  198. test_nonoptionals(conn);
  199. test_bad_tuples(conn);
  200. std::cout << "testing `std::unique_ptr` as optional...\n";
  201. test_optional<std::unique_ptr>(conn);
  202. std::cout << "testing `std::optional` as optional...\n";
  203. test_optional<std::optional>(conn);
  204. }
  205. void test_stream_from_does_escaping()
  206. {
  207. std::string const input{"a\t\n\n\n \\b\nc"};
  208. pqxx::connection conn;
  209. pqxx::work tx{conn};
  210. tx.exec0("CREATE TEMP TABLE badstr (str text)");
  211. tx.exec0("INSERT INTO badstr (str) VALUES (" + tx.quote(input) + ")");
  212. auto reader{pqxx::stream_from::table(tx, {"badstr"})};
  213. std::tuple<std::string> out;
  214. reader >> out;
  215. PQXX_CHECK_EQUAL(
  216. std::get<0>(out), input, "stream_from got weird characters wrong.");
  217. }
  218. void test_stream_from_does_iteration()
  219. {
  220. pqxx::connection conn;
  221. pqxx::work tx{conn};
  222. tx.exec0("CREATE TEMP TABLE str (s text)");
  223. tx.exec0("INSERT INTO str (s) VALUES ('foo')");
  224. auto reader{pqxx::stream_from::table(tx, {"str"})};
  225. int i{0};
  226. std::string out;
  227. for (std::tuple<std::string> t : reader.iter<std::string>())
  228. {
  229. i++;
  230. out = std::get<0>(t);
  231. }
  232. PQXX_CHECK_EQUAL(i, 1, "Wrong number of iterations.");
  233. PQXX_CHECK_EQUAL(out, "foo", "Got wrong string.");
  234. tx.exec0("INSERT INTO str (s) VALUES ('bar')");
  235. i = 0;
  236. std::set<std::string> strings;
  237. auto reader2{pqxx::stream_from::table(tx, {"str"})};
  238. for (std::tuple<std::string> t : reader2.iter<std::string>())
  239. {
  240. i++;
  241. strings.insert(std::get<0>(t));
  242. }
  243. PQXX_CHECK_EQUAL(i, 2, "Wrong number of iterations.");
  244. PQXX_CHECK_EQUAL(
  245. std::size(strings), 2u, "Wrong number of strings retrieved.");
  246. PQXX_CHECK(strings.find("foo") != std::end(strings), "Missing key.");
  247. PQXX_CHECK(strings.find("bar") != std::end(strings), "Missing key.");
  248. }
  249. void test_transaction_stream_from()
  250. {
  251. pqxx::connection conn;
  252. pqxx::work tx{conn};
  253. tx.exec0("CREATE TEMP TABLE sample (id integer, name varchar)");
  254. tx.exec0("INSERT INTO sample (id, name) VALUES (321, 'something')");
  255. int items{0};
  256. int id{0};
  257. std::string name;
  258. for (auto [iid, iname] :
  259. tx.stream<int, std::string_view>("SELECT id, name FROM sample"))
  260. {
  261. items++;
  262. id = iid;
  263. name = iname;
  264. }
  265. PQXX_CHECK_EQUAL(items, 1, "Wrong number of iterations.");
  266. PQXX_CHECK_EQUAL(id, 321, "Got wrong int.");
  267. PQXX_CHECK_EQUAL(name, std::string{"something"}, "Got wrong string.");
  268. PQXX_CHECK_EQUAL(
  269. tx.query_value<int>("SELECT 4"), 4,
  270. "Loop did not relinquish transaction.");
  271. }
  272. void test_stream_from_read_row()
  273. {
  274. pqxx::connection conn;
  275. pqxx::work tx{conn};
  276. tx.exec0("CREATE TEMP TABLE sample (id integer, name varchar, opt integer)");
  277. tx.exec0("INSERT INTO sample (id, name) VALUES (321, 'something')");
  278. auto stream{pqxx::stream_from::table(tx, {"sample"})};
  279. auto fields{stream.read_row()};
  280. PQXX_CHECK_EQUAL(fields->size(), 3ul, "Wrong number of fields.");
  281. PQXX_CHECK_EQUAL(
  282. std::string((*fields)[0]), "321", "Integer field came out wrong.");
  283. PQXX_CHECK_EQUAL(
  284. std::string((*fields)[1]), "something", "Text field came out wrong.");
  285. PQXX_CHECK(std::data((*fields)[2]) == nullptr, "Null field came out wrong.");
  286. auto last{stream.read_row()};
  287. PQXX_CHECK(last == nullptr, "No null pointer at end of stream.");
  288. }
  289. PQXX_REGISTER_TEST(test_stream_from);
  290. PQXX_REGISTER_TEST(test_stream_from_does_escaping);
  291. PQXX_REGISTER_TEST(test_stream_from_does_iteration);
  292. PQXX_REGISTER_TEST(test_transaction_stream_from);
  293. PQXX_REGISTER_TEST(test_stream_from_read_row);
  294. } // namespace