test_sql_cursor.cxx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  1. #include <pqxx/cursor>
  2. #include <pqxx/transaction>
  3. #include "../test_helpers.hxx"
  4. namespace
  5. {
  6. void test_forward_sql_cursor()
  7. {
  8. pqxx::connection conn;
  9. pqxx::work tx{conn};
  10. // Plain owned, scoped, forward-only read-only cursor.
  11. pqxx::internal::sql_cursor forward(
  12. tx, "SELECT generate_series(1, 4)", "forward",
  13. pqxx::cursor_base::forward_only, pqxx::cursor_base::read_only,
  14. pqxx::cursor_base::owned, false);
  15. PQXX_CHECK_EQUAL(forward.pos(), 0, "Wrong initial position");
  16. PQXX_CHECK_EQUAL(forward.endpos(), -1, "Wrong initial endpos()");
  17. auto empty_result{forward.empty_result()};
  18. PQXX_CHECK_EQUAL(std::size(empty_result), 0, "Empty result not empty");
  19. auto displacement{0};
  20. auto one{forward.fetch(1, displacement)};
  21. PQXX_CHECK_EQUAL(std::size(one), 1, "Fetched wrong number of rows");
  22. PQXX_CHECK_EQUAL(one[0][0].as<std::string>(), "1", "Unexpected result");
  23. PQXX_CHECK_EQUAL(displacement, 1, "Wrong displacement");
  24. PQXX_CHECK_EQUAL(forward.pos(), 1, "In wrong position");
  25. auto offset{forward.move(1, displacement)};
  26. PQXX_CHECK_EQUAL(offset, 1, "Unexpected offset from move()");
  27. PQXX_CHECK_EQUAL(displacement, 1, "Unexpected displacement after move()");
  28. PQXX_CHECK_EQUAL(forward.pos(), 2, "Wrong position after move()");
  29. PQXX_CHECK_EQUAL(forward.endpos(), -1, "endpos() unexpectedly set");
  30. auto row{forward.fetch(0, displacement)};
  31. PQXX_CHECK_EQUAL(std::size(row), 0, "fetch(0, displacement) returns rows");
  32. PQXX_CHECK_EQUAL(displacement, 0, "Unexpected displacement after fetch(0)");
  33. PQXX_CHECK_EQUAL(forward.pos(), 2, "fetch(0, displacement) affected pos()");
  34. row = forward.fetch(0);
  35. PQXX_CHECK_EQUAL(std::size(row), 0, "fetch(0) fetched wrong number of rows");
  36. PQXX_CHECK_EQUAL(forward.pos(), 2, "fetch(0) moved cursor");
  37. PQXX_CHECK_EQUAL(forward.pos(), 2, "fetch(0) affected pos()");
  38. offset = forward.move(1);
  39. PQXX_CHECK_EQUAL(offset, 1, "move(1) returned unexpected value");
  40. PQXX_CHECK_EQUAL(forward.pos(), 3, "move(1) after fetch(0) broke");
  41. row = forward.fetch(1);
  42. PQXX_CHECK_EQUAL(
  43. std::size(row), 1, "fetch(1) returned wrong number of rows");
  44. PQXX_CHECK_EQUAL(forward.pos(), 4, "fetch(1) results in bad pos()");
  45. PQXX_CHECK_EQUAL(row[0][0].as<std::string>(), "4", "pos() is lying");
  46. empty_result = forward.fetch(1, displacement);
  47. PQXX_CHECK_EQUAL(std::size(empty_result), 0, "Got rows at end of cursor");
  48. PQXX_CHECK_EQUAL(forward.pos(), 5, "Not at one-past-end position");
  49. PQXX_CHECK_EQUAL(forward.endpos(), 5, "Failed to notice end position");
  50. PQXX_CHECK_EQUAL(displacement, 1, "Wrong displacement at end position");
  51. offset = forward.move(5, displacement);
  52. PQXX_CHECK_EQUAL(offset, 0, "move() lied at end of result set");
  53. PQXX_CHECK_EQUAL(forward.pos(), 5, "pos() is beyond end");
  54. PQXX_CHECK_EQUAL(forward.endpos(), 5, "endpos() changed after end position");
  55. PQXX_CHECK_EQUAL(displacement, 0, "Wrong displacement after end position");
  56. // Move through entire result set at once.
  57. pqxx::internal::sql_cursor forward2(
  58. tx, "SELECT generate_series(1, 4)", "forward",
  59. pqxx::cursor_base::forward_only, pqxx::cursor_base::read_only,
  60. pqxx::cursor_base::owned, false);
  61. // Move through entire result set at once.
  62. offset = forward2.move(pqxx::cursor_base::all(), displacement);
  63. PQXX_CHECK_EQUAL(offset, 4, "Unexpected number of rows in result set");
  64. PQXX_CHECK_EQUAL(displacement, 5, "displacement != rows+1");
  65. PQXX_CHECK_EQUAL(forward2.pos(), 5, "Bad pos() after skipping all rows");
  66. PQXX_CHECK_EQUAL(forward2.endpos(), 5, "Bad endpos() after skipping");
  67. pqxx::internal::sql_cursor forward3(
  68. tx, "SELECT generate_series(1, 4)", "forward",
  69. pqxx::cursor_base::forward_only, pqxx::cursor_base::read_only,
  70. pqxx::cursor_base::owned, false);
  71. // Fetch entire result set at once.
  72. auto rows{forward3.fetch(pqxx::cursor_base::all(), displacement)};
  73. PQXX_CHECK_EQUAL(
  74. std::size(rows), 4, "Unexpected number of rows in result set");
  75. PQXX_CHECK_EQUAL(displacement, 5, "displacement != rows+1");
  76. PQXX_CHECK_EQUAL(forward3.pos(), 5, "Bad pos() after fetching all rows");
  77. PQXX_CHECK_EQUAL(forward3.endpos(), 5, "Bad endpos() after fetching");
  78. pqxx::internal::sql_cursor forward_empty(
  79. tx, "SELECT generate_series(0, -1)", "forward_empty",
  80. pqxx::cursor_base::forward_only, pqxx::cursor_base::read_only,
  81. pqxx::cursor_base::owned, false);
  82. offset = forward_empty.move(3, displacement);
  83. PQXX_CHECK_EQUAL(forward_empty.pos(), 1, "Bad pos() at end of result");
  84. PQXX_CHECK_EQUAL(forward_empty.endpos(), 1, "Bad endpos() in empty result");
  85. PQXX_CHECK_EQUAL(displacement, 1, "Bad displacement in empty result");
  86. PQXX_CHECK_EQUAL(offset, 0, "move() in empty result counted rows");
  87. }
  88. void test_scroll_sql_cursor()
  89. {
  90. pqxx::connection conn;
  91. pqxx::work tx{conn};
  92. pqxx::internal::sql_cursor scroll(
  93. tx, "SELECT generate_series(1, 10)", "scroll",
  94. pqxx::cursor_base::random_access, pqxx::cursor_base::read_only,
  95. pqxx::cursor_base::owned, false);
  96. PQXX_CHECK_EQUAL(scroll.pos(), 0, "Scroll cursor's initial pos() is wrong");
  97. PQXX_CHECK_EQUAL(scroll.endpos(), -1, "New scroll cursor has endpos() set");
  98. auto rows{scroll.fetch(pqxx::cursor_base::next())};
  99. PQXX_CHECK_EQUAL(std::size(rows), 1, "Scroll cursor is broken");
  100. PQXX_CHECK_EQUAL(scroll.pos(), 1, "Scroll cursor's pos() is broken");
  101. PQXX_CHECK_EQUAL(scroll.endpos(), -1, "endpos() set prematurely");
  102. // Turn cursor around. This is where we begin to feel SQL cursors'
  103. // semantics: we pre-decrement, ending up on the position in front of the
  104. // first row and returning no rows.
  105. rows = scroll.fetch(pqxx::cursor_base::prior());
  106. PQXX_CHECK_EQUAL(std::empty(rows), true, "Turning around on fetch() broke");
  107. PQXX_CHECK_EQUAL(scroll.pos(), 0, "pos() is not back at zero");
  108. PQXX_CHECK_EQUAL(
  109. scroll.endpos(), -1, "endpos() set on wrong side of result");
  110. // Bounce off the left-hand side of the result set. Can't move before the
  111. // starting position.
  112. auto offset{0}, displacement{0};
  113. offset = scroll.move(-3, displacement);
  114. PQXX_CHECK_EQUAL(offset, 0, "Rows found before beginning");
  115. PQXX_CHECK_EQUAL(displacement, 0, "Failed to bounce off beginning");
  116. PQXX_CHECK_EQUAL(scroll.pos(), 0, "pos() moved back from zero");
  117. PQXX_CHECK_EQUAL(scroll.endpos(), -1, "endpos() set on left-side bounce");
  118. // Try bouncing off the left-hand side a little harder. Take 4 paces away
  119. // from the boundary and run into it.
  120. offset = scroll.move(4, displacement);
  121. PQXX_CHECK_EQUAL(offset, 4, "Offset mismatch");
  122. PQXX_CHECK_EQUAL(displacement, 4, "Displacement mismatch");
  123. PQXX_CHECK_EQUAL(scroll.pos(), 4, "Position mismatch");
  124. PQXX_CHECK_EQUAL(scroll.endpos(), -1, "endpos() set at weird time");
  125. offset = scroll.move(-10, displacement);
  126. PQXX_CHECK_EQUAL(offset, 3, "Offset mismatch");
  127. PQXX_CHECK_EQUAL(displacement, -4, "Displacement mismatch");
  128. PQXX_CHECK_EQUAL(scroll.pos(), 0, "Hard bounce failed");
  129. PQXX_CHECK_EQUAL(scroll.endpos(), -1, "endpos() set during hard bounce");
  130. rows = scroll.fetch(3);
  131. PQXX_CHECK_EQUAL(scroll.pos(), 3, "Bad pos()");
  132. PQXX_CHECK_EQUAL(std::size(rows), 3, "Wrong number of rows");
  133. PQXX_CHECK_EQUAL(rows[2][0].as<int>(), 3, "pos() does not match data");
  134. rows = scroll.fetch(-1);
  135. PQXX_CHECK_EQUAL(scroll.pos(), 2, "Bad pos()");
  136. PQXX_CHECK_EQUAL(rows[0][0].as<int>(), 2, "pos() does not match data");
  137. rows = scroll.fetch(1);
  138. PQXX_CHECK_EQUAL(scroll.pos(), 3, "Bad pos() after inverse turnaround");
  139. PQXX_CHECK_EQUAL(rows[0][0].as<int>(), 3, "Data position mismatch");
  140. }
  141. void test_adopted_sql_cursor()
  142. {
  143. pqxx::connection conn;
  144. pqxx::work tx{conn};
  145. tx.exec0(
  146. "DECLARE adopted SCROLL CURSOR FOR "
  147. "SELECT generate_series(1, 3)");
  148. pqxx::internal::sql_cursor adopted(tx, "adopted", pqxx::cursor_base::owned);
  149. PQXX_CHECK_EQUAL(adopted.pos(), -1, "Adopted cursor has known pos()");
  150. PQXX_CHECK_EQUAL(adopted.endpos(), -1, "Adopted cursor has known endpos()");
  151. auto displacement{0};
  152. auto rows{adopted.fetch(pqxx::cursor_base::all(), displacement)};
  153. PQXX_CHECK_EQUAL(std::size(rows), 3, "Wrong number of rows in result");
  154. PQXX_CHECK_EQUAL(rows[0][0].as<int>(), 1, "Wrong result data");
  155. PQXX_CHECK_EQUAL(rows[2][0].as<int>(), 3, "Wrong result data");
  156. PQXX_CHECK_EQUAL(displacement, 4, "Wrong displacement");
  157. PQXX_CHECK_EQUAL(
  158. adopted.pos(), -1, "End-of-result set pos() on adopted cur");
  159. PQXX_CHECK_EQUAL(adopted.endpos(), -1, "endpos() set too early");
  160. rows = adopted.fetch(pqxx::cursor_base::backward_all(), displacement);
  161. PQXX_CHECK_EQUAL(std::size(rows), 3, "Wrong number of rows in result");
  162. PQXX_CHECK_EQUAL(rows[0][0].as<int>(), 3, "Wrong result data");
  163. PQXX_CHECK_EQUAL(rows[2][0].as<int>(), 1, "Wrong result data");
  164. PQXX_CHECK_EQUAL(displacement, -4, "Wrong displacement");
  165. PQXX_CHECK_EQUAL(adopted.pos(), 0, "Failed to recognize starting position");
  166. PQXX_CHECK_EQUAL(adopted.endpos(), -1, "endpos() set too early");
  167. auto offset{adopted.move(pqxx::cursor_base::all())};
  168. PQXX_CHECK_EQUAL(offset, 3, "Unexpected move() offset");
  169. PQXX_CHECK_EQUAL(adopted.pos(), 4, "Bad position on adopted cursor");
  170. PQXX_CHECK_EQUAL(adopted.endpos(), 4, "endpos() not set properly");
  171. // Owned adopted cursors are cleaned up on destruction.
  172. pqxx::connection conn2;
  173. pqxx::work tx2(conn2, "tx2");
  174. tx2.exec0(
  175. "DECLARE adopted2 CURSOR FOR "
  176. "SELECT generate_series(1, 3)");
  177. {
  178. pqxx::internal::sql_cursor(tx2, "adopted2", pqxx::cursor_base::owned);
  179. }
  180. // Modern backends: accessing the cursor now is an error, as you'd expect.
  181. PQXX_CHECK_THROWS(
  182. tx2.exec("FETCH 1 IN adopted2"), pqxx::sql_error,
  183. "Owned adopted cursor not cleaned up");
  184. tx2.abort();
  185. pqxx::work tx3(conn2, "tx3");
  186. tx3.exec(
  187. "DECLARE adopted3 CURSOR FOR "
  188. "SELECT generate_series(1, 3)");
  189. {
  190. pqxx::internal::sql_cursor(tx3, "adopted3", pqxx::cursor_base::loose);
  191. }
  192. tx3.exec("MOVE 1 IN adopted3");
  193. }
  194. void test_hold_cursor()
  195. {
  196. pqxx::connection conn;
  197. pqxx::work tx{conn};
  198. // "With hold" cursor is kept after commit.
  199. pqxx::internal::sql_cursor with_hold(
  200. tx, "SELECT generate_series(1, 3)", "hold_cursor",
  201. pqxx::cursor_base::forward_only, pqxx::cursor_base::read_only,
  202. pqxx::cursor_base::owned, true);
  203. tx.commit();
  204. pqxx::work tx2(conn, "tx2");
  205. auto rows{with_hold.fetch(1)};
  206. PQXX_CHECK_EQUAL(
  207. std::size(rows), 1, "Did not get 1 row from with-hold cursor");
  208. // Cursor without hold is closed on commit.
  209. pqxx::internal::sql_cursor no_hold(
  210. tx2, "SELECT generate_series(1, 3)", "no_hold_cursor",
  211. pqxx::cursor_base::forward_only, pqxx::cursor_base::read_only,
  212. pqxx::cursor_base::owned, false);
  213. tx2.commit();
  214. pqxx::work tx3(conn, "tx3");
  215. PQXX_CHECK_THROWS(
  216. no_hold.fetch(1), pqxx::sql_error, "Cursor not closed on commit");
  217. }
  218. void cursor_tests()
  219. {
  220. test_forward_sql_cursor();
  221. test_scroll_sql_cursor();
  222. test_adopted_sql_cursor();
  223. test_hold_cursor();
  224. }
  225. PQXX_REGISTER_TEST(cursor_tests);
  226. } // namespace