text: /** Implementation of libpqxx STL-style cursor classes.
   *
   * These classes wrap SQL cursors in STL-like interfaces.
   *
   * Copyright (c) 2004-2019, Jeroen T. Vermeulen.
   *
   * See COPYING for copyright license.  If you did not receive a file called
   * COPYING with this source code, please notify the distributor of this mistake,
   * or contact the author.
   */
  #include "pqxx/compiler-internal.hxx"
  
  #include <iterator>
  
  #include "pqxx/cursor"
  
  #include "pqxx/internal/encodings.hxx"
  #include "pqxx/internal/gates/connection-sql_cursor.hxx"
  #include "pqxx/internal/gates/transaction-sql_cursor.hxx"
  
  using namespace pqxx;
  using namespace pqxx::internal;
  
  
  namespace
  {
  /// Is this character a "useless trailing character" in a query?
  /** A character is "useless" at the end of a query if it is either whitespace or
   * a semicolon.
   */
  inline bool useless_trail(char c)
  {
    return isspace(c) or c==';';
  }
  
  
  /// Find end of nonempty query, stripping off any trailing semicolon.
  /** When executing a normal query, a trailing semicolon is meaningless but
   * won't hurt.  That's why we can't rule out that some code may include one.
   *
   * But for cursor queries, a trailing semicolon is a problem.  The query gets
   * embedded in a larger statement, which a semicolon would break into two.
   * We'll have to remove it if present.
   *
   * A trailing semicolon may not actually be at the end.  It could be masked by
   * subsequent whitespace.  If there's also a comment though, that's the
   * caller's own lookout.  We can't guard against every possible mistake, and
   * text processing is actually remarkably sensitive to mistakes in a
   * multi-encoding world.
   *
   * If there is a trailing semicolon, this function returns its offset.  If
   * there are more than one, it returns the offset of the first one.  If there
   * is no trailing semicolon, it returns the length of the query string.
   *
   * The query must be nonempty.
   */
  std::string::size_type find_query_end(
  	const std::string &query,
  	encoding_group enc)
  {
    const auto text = query.c_str();
    const auto size = query.size();
    std::string::size_type end;
    if (enc == encoding_group::MONOBYTE)
    {
      // This is an encoding where we can scan backwards from the end.
      for (end = query.size(); end > 0 and useless_trail(text[end-1]); --end);
    }
    else
    {
      // Complex encoding.  We only know how to iterate forwards, so start from
      // the beginning.
      end = 0;
  
      pqxx::internal::for_glyphs(
          enc,
          [text, &end](const char *gbegin, const char *gend)
          {
            if (gend - gbegin > 1 or not useless_trail(*gbegin))
              end = std::string::size_type(gend - text);
          },
          text, size);
    }
  
    return end;
  }
  } // namespace
  
  
  pqxx::internal::sql_cursor::sql_cursor(
  	transaction_base &t,
  	const std::string &query,
  	const std::string &cname,
  	cursor_base::accesspolicy ap,
  	cursor_base::updatepolicy up,
  	cursor_base::ownershippolicy op,
  	bool hold) :
    cursor_base{t.conn(), cname},
    m_home{t.conn()},
    m_adopted{false},
    m_at_end{-1},
    m_pos{0}
  {
    if (&t.conn() != &m_home) throw internal_error{"Cursor in wrong connection"};
  
  #include "pqxx/internal/ignore-deprecated-pre.hxx"
    m_home.activate();
  #include "pqxx/internal/ignore-deprecated-post.hxx"
  
    if (query.empty()) throw usage_error{"Cursor has empty query."};
    const auto enc = enc_group(t.conn().encoding_id());
    const auto qend = find_query_end(query, enc);
    if (qend == 0) throw usage_error{"Cursor has effectively empty query."};
  
    std::stringstream cq, qn;
  
    cq << "DECLARE " << t.quote_name(name()) << " ";
  
    if (ap == cursor_base::forward_only) cq << "NO ";
    cq << "SCROLL ";
  
    cq << "CURSOR ";
  
    if (hold) cq << "WITH HOLD ";
  
    cq << "FOR ";
    cq.write(query.c_str(), std::streamsize(qend));
    cq << ' ';
  
    if (up != cursor_base::update) cq << "FOR READ ONLY ";
    else cq << "FOR UPDATE ";
  
    qn << "[DECLARE " << name() << ']';
    t.exec(cq, qn.str());
  
    // Now that we're here in the starting position, keep a copy of an empty
    // result.  That may come in handy later, because we may not be able to
    // construct an empty result with all the right metadata due to the weird
    // meaning of "FETCH 0."
    init_empty_result(t);
  
    // If we're creating a WITH HOLD cursor, noone is going to destroy it until
    // after this transaction.  That means the connection cannot be deactivated
    // without losing the cursor.
    if (hold)
      gate::connection_sql_cursor{t.conn()}.add_reactivation_avoidance_count(1);
  
    m_ownership = op;
  }
  
  
  pqxx::internal::sql_cursor::sql_cursor(
  	transaction_base &t,
  	const std::string &cname,
  	cursor_base::ownershippolicy op) :
    cursor_base{t.conn(), cname, false},
    m_home{t.conn()},
    m_empty_result{},
    m_adopted{true},
    m_at_end{0},
    m_pos{-1}
  {
    // If we take responsibility for destroying the cursor, that's one less
    // reason not to allow the connection to be deactivated and reactivated.
    // TODO: Go over lifetime/reactivation rules again to be sure they work.
    if (op==cursor_base::owned)
      gate::connection_sql_cursor{t.conn()}.add_reactivation_avoidance_count(-1);
    m_adopted = true;
    m_ownership = op;
  }
  
  
  void pqxx::internal::sql_cursor::close() noexcept
  {
    if (m_ownership==cursor_base::owned)
    {
      try
      {
        gate::connection_sql_cursor{m_home}.exec(
  	("CLOSE " + m_home.quote_name(name())).c_str(),
  	0);
      }
      catch (const std::exception &)
      {
      }
  
      if (m_adopted)
        gate::connection_sql_cursor{m_home}.add_reactivation_avoidance_count(-1);
  
      m_ownership = cursor_base::loose;
    }
  }
  
  
  void pqxx::internal::sql_cursor::init_empty_result(transaction_base &t)
  {
    if (pos() != 0) throw internal_error{"init_empty_result() from bad pos()."};
    m_empty_result = t.exec("FETCH 0 IN " + m_home.quote_name(name()));
  }
  
  
  /// Compute actual displacement based on requested and reported displacements.
  internal::sql_cursor::difference_type
  pqxx::internal::sql_cursor::adjust(difference_type hoped,
  	difference_type actual)
  {
    if (actual < 0) throw internal_error{"Negative rows in cursor movement."};
    if (hoped == 0) return 0;
    const int direction = ((hoped < 0) ? -1 : 1);
    bool hit_end = false;
    if (actual != labs(hoped))
    {
      if (actual > labs(hoped))
        throw internal_error{"Cursor displacement larger than requested."};
  
      // If we see fewer rows than requested, then we've hit an end (on either
      // side) of the result set.  Wether we make an extra step to a one-past-end
      // position or whether we're already there depends on where we were
      // previously: if our last move was in the same direction and also fell
      // short, we're already at a one-past-end row.
      if (m_at_end != direction) ++actual;
  
      // If we hit the beginning, make sure our position calculation ends up
      // at zero (even if we didn't previously know where we were!), and if we
      // hit the other end, register the fact that we now know where the end
      // of the result set is.
      if (direction > 0) hit_end = true;
      else if (m_pos == -1) m_pos = actual;
      else if (m_pos != actual)
        throw internal_error{
  	"Moved back to beginning, but wrong position: "
          "hoped=" + to_string(hoped) + ", "
          "actual=" + to_string(actual) + ", "
          "m_pos=" + to_string(m_pos) + ", "
          "direction=" + to_string(direction) + "."};
  
      m_at_end = direction;
    }
    else
    {
      m_at_end = 0;
    }
  
    if (m_pos >= 0) m_pos += direction*actual;
    if (hit_end)
    {
      if (m_endpos >= 0 and m_pos != m_endpos)
        throw internal_error{"Inconsistent cursor end positions."};
      m_endpos = m_pos;
    }
    return direction*actual;
  }
  
  
  result pqxx::internal::sql_cursor::fetch(
  	difference_type rows,
  	difference_type &displacement)
  {
    if (rows == 0)
    {
      displacement = 0;
      return m_empty_result;
    }
    const std::string query =
        "FETCH " + stridestring(rows) + " IN " + m_home.quote_name(name());
    const result r{gate::connection_sql_cursor{m_home}.exec(query.c_str(), 0)};
    displacement = adjust(rows, difference_type(r.size()));
    return r;
  }
  
  
  cursor_base::difference_type pqxx::internal::sql_cursor::move(
  	difference_type rows,
  	difference_type &displacement)
  {
    if (rows == 0)
    {
      displacement = 0;
      return 0;
    }
  
    const std::string query =
        "MOVE " + stridestring(rows) + " IN " + m_home.quote_name(name());
    const result r(gate::connection_sql_cursor{m_home}.exec(query.c_str(), 0));
    difference_type d = difference_type(r.affected_rows());
    displacement = adjust(rows, d);
    return d;
  }
  
  
  std::string pqxx::internal::sql_cursor::stridestring(difference_type n)
  {
    /* Some special-casing for ALL and BACKWARD ALL here.  We used to use numeric
     * "infinities" for difference_type for this (the highest and lowest possible
     * values for "long"), but for PostgreSQL 8.0 at least, the backend appears to
     * expect a 32-bit number and fails to parse large 64-bit numbers.
     * We could change the alias to match this behaviour, but that would break
     * if/when Postgres is changed to accept 64-bit displacements.
     */
    static const std::string All{"ALL"}, BackAll{"BACKWARD ALL"};
    if (n >= cursor_base::all()) return All;
    else if (n <= cursor_base::backward_all()) return BackAll;
    return to_string(n);
  }
  