test_stream_to.cxx 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445
  1. #include <iostream>
  2. #include <optional>
  3. #include <pqxx/stream_to>
  4. #include <pqxx/transaction>
  5. #include "../test_helpers.hxx"
  6. #include "../test_types.hxx"
  7. namespace
  8. {
  9. std::string truncate_sql_error(std::string const &what)
  10. {
  11. auto trunc{what.substr(0, what.find('\n'))};
  12. if (std::size(trunc) > 64)
  13. trunc = trunc.substr(0, 61) + "...";
  14. return trunc;
  15. }
  16. void test_nonoptionals(pqxx::connection &connection)
  17. {
  18. pqxx::work tx{connection};
  19. auto inserter{pqxx::stream_to::table(tx, {"stream_to_test"})};
  20. PQXX_CHECK(inserter, "stream_to failed to initialize");
  21. auto const nonascii{"\u3053\u3093\u306b\u3061\u308f"};
  22. bytea const binary{'\x00', '\x01', '\x02'},
  23. text{'f', 'o', 'o', ' ', 'b', 'a', 'r', '\0'};
  24. inserter << std::make_tuple(
  25. 1234, "now", 4321, ipv4{8, 8, 4, 4}, "hello nonoptional world", binary);
  26. inserter << std::make_tuple(
  27. 5678, "2018-11-17 21:23:00", nullptr, nullptr, nonascii, text);
  28. inserter << std::make_tuple(910, nullptr, nullptr, nullptr, "\\N", bytea{});
  29. inserter.complete();
  30. auto r1{tx.exec1("SELECT * FROM stream_to_test WHERE number0 = 1234")};
  31. PQXX_CHECK_EQUAL(r1[0].as<int>(), 1234, "Read back wrong first int.");
  32. PQXX_CHECK_EQUAL(
  33. r1[4].as<std::string>(), "hello nonoptional world",
  34. "Read back wrong string.");
  35. PQXX_CHECK_EQUAL(r1[3].as<ipv4>(), ipv4(8, 8, 4, 4), "Read back wrong ip.");
  36. PQXX_CHECK_EQUAL(r1[5].as<bytea>(), binary, "Read back wrong bytea.");
  37. auto r2{tx.exec1("SELECT * FROM stream_to_test WHERE number0 = 5678")};
  38. PQXX_CHECK_EQUAL(r2[0].as<int>(), 5678, "Wrong int on second row.");
  39. PQXX_CHECK(r2[2].is_null(), "Field 2 was meant to be null.");
  40. PQXX_CHECK(r2[3].is_null(), "Field 3 was meant to be null.");
  41. PQXX_CHECK_EQUAL(r2[4].as<std::string>(), nonascii, "Wrong non-ascii text.");
  42. tx.commit();
  43. }
  44. void test_nonoptionals_fold(pqxx::connection &connection)
  45. {
  46. pqxx::work tx{connection};
  47. auto inserter{pqxx::stream_to::table(tx, {"stream_to_test"})};
  48. PQXX_CHECK(inserter, "stream_to failed to initialize");
  49. auto const nonascii{"\u3053\u3093\u306b\u3061\u308f"};
  50. bytea const binary{'\x00', '\x01', '\x02'},
  51. text{'f', 'o', 'o', ' ', 'b', 'a', 'r', '\0'};
  52. inserter.write_values(
  53. 1234, "now", 4321, ipv4{8, 8, 4, 4}, "hello nonoptional world", binary);
  54. inserter.write_values(
  55. 5678, "2018-11-17 21:23:00", nullptr, nullptr, nonascii, text);
  56. inserter.write_values(910, nullptr, nullptr, nullptr, "\\N", bytea{});
  57. inserter.complete();
  58. auto r1{tx.exec1("SELECT * FROM stream_to_test WHERE number0 = 1234")};
  59. PQXX_CHECK_EQUAL(r1[0].as<int>(), 1234, "Read back wrong first int.");
  60. PQXX_CHECK_EQUAL(
  61. r1[4].as<std::string>(), "hello nonoptional world",
  62. "Read back wrong string.");
  63. PQXX_CHECK_EQUAL(r1[3].as<ipv4>(), ipv4(8, 8, 4, 4), "Read back wrong ip.");
  64. PQXX_CHECK_EQUAL(r1[5].as<bytea>(), binary, "Read back wrong bytera.");
  65. auto r2{tx.exec1("SELECT * FROM stream_to_test WHERE number0 = 5678")};
  66. PQXX_CHECK_EQUAL(r2[0].as<int>(), 5678, "Wrong int on second row.");
  67. PQXX_CHECK(r2[2].is_null(), "Field 2 was meant to be null.");
  68. PQXX_CHECK(r2[3].is_null(), "Field 3 was meant to be null.");
  69. PQXX_CHECK_EQUAL(r2[4].as<std::string>(), nonascii, "Wrong non-ascii text.");
  70. tx.commit();
  71. }
  72. /// Try to violate stream_to_test's not-null constraint using a stream_to.
  73. void insert_bad_null_tuple(pqxx::stream_to &inserter)
  74. {
  75. inserter << std::make_tuple(
  76. nullptr, "now", 4321, ipv4{8, 8, 8, 8}, "hello world",
  77. bytea{'\x00', '\x01', '\x02'});
  78. inserter.complete();
  79. }
  80. void test_bad_null(pqxx::connection &connection)
  81. {
  82. pqxx::work tx{connection};
  83. auto inserter{pqxx::stream_to::table(tx, {"stream_to_test"})};
  84. PQXX_CHECK(inserter, "stream_to failed to initialize");
  85. PQXX_CHECK_THROWS(
  86. insert_bad_null_tuple(inserter), pqxx::not_null_violation,
  87. "Did not expected not_null_violation when stream_to inserts a bad null.");
  88. }
  89. /// Try to violate stream_to_test's not-null construct using a stream_to.
  90. void insert_bad_null_write(pqxx::stream_to &inserter)
  91. {
  92. inserter.write_values(
  93. nullptr, "now", 4321, ipv4{8, 8, 8, 8}, "hello world",
  94. bytea{'\x00', '\x01', '\x02'});
  95. inserter.complete();
  96. }
  97. void test_bad_null_fold(pqxx::connection &connection)
  98. {
  99. pqxx::work tx{connection};
  100. auto inserter{pqxx::stream_to::table(tx, {"stream_to_test"})};
  101. PQXX_CHECK(inserter, "stream_to failed to initialize");
  102. PQXX_CHECK_THROWS(
  103. insert_bad_null_write(inserter), pqxx::not_null_violation,
  104. "Did not expected not_null_violation when stream_to inserts a bad null.");
  105. }
  106. void test_too_few_fields(pqxx::connection &connection)
  107. {
  108. pqxx::work tx{connection};
  109. auto inserter{pqxx::stream_to::table(tx, {"stream_to_test"})};
  110. PQXX_CHECK(inserter, "stream_to failed to initialize");
  111. try
  112. {
  113. inserter << std::make_tuple(1234, "now", 4321, ipv4{8, 8, 8, 8});
  114. inserter.complete();
  115. tx.commit();
  116. PQXX_CHECK_NOTREACHED("stream_from improperly inserted row");
  117. }
  118. catch (pqxx::sql_error const &e)
  119. {
  120. std::string what{e.what()};
  121. if (what.find("missing data for column") == std::string::npos)
  122. throw;
  123. pqxx::test::expected_exception(
  124. "Could not insert row: " + truncate_sql_error(what));
  125. }
  126. }
  127. void test_too_few_fields_fold(pqxx::connection &connection)
  128. {
  129. pqxx::work tx{connection};
  130. auto inserter{pqxx::stream_to::table(tx, {"stream_to_test"})};
  131. PQXX_CHECK(inserter, "stream_to failed to initialize");
  132. try
  133. {
  134. inserter.write_values(1234, "now", 4321, ipv4{8, 8, 8, 8});
  135. inserter.complete();
  136. tx.commit();
  137. PQXX_CHECK_NOTREACHED("stream_from_fold improperly inserted row");
  138. }
  139. catch (pqxx::sql_error const &e)
  140. {
  141. std::string what{e.what()};
  142. if (what.find("missing data for column") == std::string::npos)
  143. throw;
  144. pqxx::test::expected_exception(
  145. "Fold - Could not insert row: " + truncate_sql_error(what));
  146. }
  147. }
  148. void test_too_many_fields(pqxx::connection &connection)
  149. {
  150. pqxx::work tx{connection};
  151. auto inserter{pqxx::stream_to::table(tx, {"stream_to_test"})};
  152. PQXX_CHECK(inserter, "stream_to failed to initialize");
  153. try
  154. {
  155. inserter << std::make_tuple(
  156. 1234, "now", 4321, ipv4{8, 8, 8, 8}, "hello world",
  157. bytea{'\x00', '\x01', '\x02'}, 5678);
  158. inserter.complete();
  159. tx.commit();
  160. PQXX_CHECK_NOTREACHED("stream_from improperly inserted row");
  161. }
  162. catch (pqxx::sql_error const &e)
  163. {
  164. std::string what{e.what()};
  165. if (what.find("extra data") == std::string::npos)
  166. throw;
  167. pqxx::test::expected_exception(
  168. "Could not insert row: " + truncate_sql_error(what));
  169. }
  170. }
  171. void test_too_many_fields_fold(pqxx::connection &connection)
  172. {
  173. pqxx::work tx{connection};
  174. auto inserter{pqxx::stream_to::table(tx, {"stream_to_test"})};
  175. PQXX_CHECK(inserter, "stream_to failed to initialize");
  176. try
  177. {
  178. inserter.write_values(
  179. 1234, "now", 4321, ipv4{8, 8, 8, 8}, "hello world",
  180. bytea{'\x00', '\x01', '\x02'}, 5678);
  181. inserter.complete();
  182. tx.commit();
  183. PQXX_CHECK_NOTREACHED("stream_from_fold improperly inserted row");
  184. }
  185. catch (pqxx::sql_error const &e)
  186. {
  187. std::string what{e.what()};
  188. if (what.find("extra data") == std::string::npos)
  189. throw;
  190. pqxx::test::expected_exception(
  191. "Fold - Could not insert row: " + truncate_sql_error(what));
  192. }
  193. }
  194. void test_stream_to_does_nonnull_optional()
  195. {
  196. pqxx::connection conn;
  197. pqxx::work tx{conn};
  198. tx.exec0("CREATE TEMP TABLE foo(x integer, y text)");
  199. auto inserter{pqxx::stream_to::table(tx, {"foo"})};
  200. inserter.write_values(
  201. std::optional<int>{368}, std::optional<std::string>{"Text"});
  202. inserter.complete();
  203. auto const row{tx.exec1("SELECT x, y FROM foo")};
  204. PQXX_CHECK_EQUAL(
  205. row[0].as<std::string>(), "368", "Non-null int optional came out wrong.");
  206. PQXX_CHECK_EQUAL(
  207. row[1].as<std::string>(), "Text",
  208. "Non-null string optional came out wrong.");
  209. }
  210. template<template<typename...> class O>
  211. void test_optional(pqxx::connection &connection)
  212. {
  213. pqxx::work tx{connection};
  214. auto inserter{pqxx::stream_to::table(tx, {"stream_to_test"})};
  215. PQXX_CHECK(inserter, "stream_to failed to initialize");
  216. inserter << std::make_tuple(
  217. 910, O<std::string>{pqxx::nullness<O<std::string>>::null()},
  218. O<int>{pqxx::nullness<O<int>>::null()},
  219. O<ipv4>{pqxx::nullness<O<ipv4>>::null()}, "\\N", bytea{});
  220. inserter.complete();
  221. tx.commit();
  222. }
  223. template<template<typename...> class O>
  224. void test_optional_fold(pqxx::connection &connection)
  225. {
  226. pqxx::work tx{connection};
  227. auto inserter{pqxx::stream_to::table(tx, {"stream_to_test"})};
  228. PQXX_CHECK(inserter, "stream_to failed to initialize");
  229. inserter.write_values(
  230. 910, O<std::string>{pqxx::nullness<O<std::string>>::null()},
  231. O<int>{pqxx::nullness<O<int>>::null()},
  232. O<ipv4>{pqxx::nullness<O<ipv4>>::null()}, "\\N", bytea{});
  233. inserter.complete();
  234. tx.commit();
  235. }
  236. // As an alternative to a tuple, you can also insert a container.
  237. void test_container_stream_to()
  238. {
  239. pqxx::connection conn;
  240. pqxx::work tx{conn};
  241. tx.exec0("CREATE TEMP TABLE test_container(a integer, b integer)");
  242. auto inserter{pqxx::stream_to::table(tx, {"test_container"})};
  243. inserter << std::vector{112, 244};
  244. inserter.complete();
  245. auto read{tx.exec1("SELECT * FROM test_container")};
  246. PQXX_CHECK_EQUAL(
  247. read[0].as<int>(), 112, "stream_to on container went wrong.");
  248. PQXX_CHECK_EQUAL(
  249. read[1].as<int>(), 244, "Second container field went wrong.");
  250. tx.commit();
  251. }
  252. void test_variant_fold(pqxx::connection_base &connection)
  253. {
  254. pqxx::work tx{connection};
  255. auto inserter{pqxx::stream_to::table(tx, {"stream_to_test"})};
  256. PQXX_CHECK(inserter, "stream_to failed to initialize");
  257. inserter.write_values(
  258. std::variant<std::string, int>{1234},
  259. std::variant<float, std::string>{"now"}, 4321, ipv4{8, 8, 8, 8},
  260. "hello world", bytea{'\x00', '\x01', '\x02'});
  261. inserter.write_values(
  262. 5678, "2018-11-17 21:23:00", nullptr, nullptr,
  263. "\u3053\u3093\u306b\u3061\u308f",
  264. bytea{'f', 'o', 'o', ' ', 'b', 'a', 'r', '\0'});
  265. inserter.write_values(910, nullptr, nullptr, nullptr, "\\N", bytea{});
  266. inserter.complete();
  267. tx.commit();
  268. }
  269. void clear_table(pqxx::connection &conn)
  270. {
  271. pqxx::work tx{conn};
  272. tx.exec0("DELETE FROM stream_to_test");
  273. tx.commit();
  274. }
  275. void test_stream_to()
  276. {
  277. pqxx::connection conn;
  278. pqxx::work tx{conn};
  279. tx.exec0(
  280. "CREATE TEMP TABLE stream_to_test ("
  281. "number0 INT NOT NULL,"
  282. "ts1 TIMESTAMP NULL,"
  283. "number2 INT NULL,"
  284. "addr3 INET NULL,"
  285. "txt4 TEXT NULL,"
  286. "bin5 BYTEA NOT NULL"
  287. ")");
  288. tx.commit();
  289. test_nonoptionals(conn);
  290. clear_table(conn);
  291. test_nonoptionals_fold(conn);
  292. clear_table(conn);
  293. test_bad_null(conn);
  294. clear_table(conn);
  295. test_bad_null_fold(conn);
  296. clear_table(conn);
  297. test_too_few_fields(conn);
  298. clear_table(conn);
  299. test_too_few_fields_fold(conn);
  300. clear_table(conn);
  301. test_too_many_fields(conn);
  302. clear_table(conn);
  303. test_too_many_fields_fold(conn);
  304. clear_table(conn);
  305. test_optional<std::unique_ptr>(conn);
  306. clear_table(conn);
  307. test_optional_fold<std::unique_ptr>(conn);
  308. clear_table(conn);
  309. test_optional<std::optional>(conn);
  310. clear_table(conn);
  311. test_optional_fold<std::optional>(conn);
  312. clear_table(conn);
  313. test_variant_fold(conn);
  314. }
  315. void test_stream_to_factory_with_static_columns()
  316. {
  317. pqxx::connection conn;
  318. pqxx::work tx{conn};
  319. tx.exec0("CREATE TEMP TABLE pqxx_stream_to(a integer, b varchar)");
  320. auto stream{pqxx::stream_to::table(tx, {"pqxx_stream_to"}, {"a", "b"})};
  321. stream.write_values(3, "three");
  322. stream.complete();
  323. auto r{tx.exec1("SELECT a, b FROM pqxx_stream_to")};
  324. PQXX_CHECK_EQUAL(r[0].as<int>(), 3, "Failed to stream_to a table.");
  325. PQXX_CHECK_EQUAL(
  326. r[1].as<std::string>(), "three",
  327. "Failed to stream_to a string to a table.");
  328. }
  329. void test_stream_to_factory_with_dynamic_columns()
  330. {
  331. pqxx::connection conn;
  332. pqxx::work tx{conn};
  333. tx.exec0("CREATE TEMP TABLE pqxx_stream_to(a integer, b varchar)");
  334. std::vector<std::string_view> columns{"a", "b"};
  335. #if defined(PQXX_HAVE_CONCEPTS)
  336. auto stream{pqxx::stream_to::table(tx, {"pqxx_stream_to"}, columns)};
  337. #else
  338. auto stream{pqxx::stream_to::raw_table(
  339. tx, conn.quote_table({"pqxx_stream_to"}), conn.quote_columns(columns))};
  340. #endif
  341. stream.write_values(4, "four");
  342. stream.complete();
  343. auto r{tx.exec1("SELECT a, b FROM pqxx_stream_to")};
  344. PQXX_CHECK_EQUAL(
  345. r[0].as<int>(), 4, "Failed to stream_to a table with dynamic columns.");
  346. PQXX_CHECK_EQUAL(
  347. r[1].as<std::string>(), "four",
  348. "Failed to stream_to a string to a table with dynamic columns.");
  349. }
  350. void test_stream_to_quotes_arguments()
  351. {
  352. pqxx::connection conn;
  353. pqxx::work tx{conn};
  354. std::string const table{R"--(pqxx_Stream"'x)--"}, column{R"--(a'"b)--"};
  355. tx.exec0(
  356. "CREATE TEMP TABLE " + tx.quote_name(table) + "(" + tx.quote_name(column) +
  357. " integer)");
  358. auto write{pqxx::stream_to::table(tx, {table}, {column})};
  359. write.write_values<int>(12);
  360. write.complete();
  361. PQXX_CHECK_EQUAL(
  362. tx.query_value<int>(
  363. "SELECT " + tx.quote_name(column) + " FROM " + tx.quote_name(table)),
  364. 12, "Stream wrote wrong value.");
  365. }
  366. PQXX_REGISTER_TEST(test_stream_to);
  367. PQXX_REGISTER_TEST(test_container_stream_to);
  368. PQXX_REGISTER_TEST(test_stream_to_does_nonnull_optional);
  369. PQXX_REGISTER_TEST(test_stream_to_factory_with_static_columns);
  370. PQXX_REGISTER_TEST(test_stream_to_factory_with_dynamic_columns);
  371. PQXX_REGISTER_TEST(test_stream_to_quotes_arguments);
  372. } // namespace