/*
 * Copyright (c) 2015, 2024, Oracle and/or its affiliates.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License, version 2.0, as
 * published by the Free Software Foundation.
 *
 * This program is designed to work with certain software (including
 * but not limited to OpenSSL) that is licensed under separate terms, as
 * designated in a particular file or component or in included license
 * documentation. The authors of MySQL hereby grant you an additional
 * permission to link the program and your derivative works with the
 * separately licensed software that they have either included with
 * the program or referenced in the documentation.
 *
 * Without limiting anything contained in the foregoing, this file,
 * which is part of Connector/C++, is also subject to the
 * Universal FOSS Exception, version 1.0, a copy of which can be found at
 * https://oss.oracle.com/licenses/universal-foss-exception.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License, version 2.0, for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
 */

#ifndef MYSQLX_COMMON_RESULT_INT_H
#define MYSQLX_COMMON_RESULT_INT_H


#include "common.h"
#include "session.h"
#include "value.h"

#include <mysql/cdk.h>
#include <mysql/cdk/converters.h>
#include <expr_parser.h>


PUSH_SYS_WARNINGS
#include <queue>
POP_SYS_WARNINGS


namespace mysqlx {
namespace impl {
namespace common {

// TODO: Use std::variant when available
using cdk::foundation::variant;


/*
  Handling result meta-data information
  =====================================

  Meta-data for result columns is provided by CDK cursor object which implements
  cdk::Meta_data interface. This information is read from cursor into
  an instance of Meta_data class in Result_impl::get_meta_data() method which
  is called from Result_impl_base::next_result() method when meta-data
  is eceived from the server. Created Meta_data instance is shared between
  result object and Row instances representing individual rows fetched from
  the result. Inside Row instance, meta-data is used to decode values from raw
  bytes.

  Textual meta-data information, such as column names, can be stored either as
  a wide string or utf8 encoded. For that reason the Meta_data and Column_info
  classes are in fact templates parametrized by class STR used to store string
  data (either std::string or some wide string class).

  Meta_data class contains a map from column positions to instances
  of Column_info class. Each Column_info instance can store meta-data
  information for a single column. The Column_info instances are created in
  the Meta_data constructor which reads meta-data information from
  cdk::Meta_data interface and adds Column_info objects to the map using add()
  methods.

  The CDK meta-data for a single column consists of:

  - CDK type constant (cdk::Type_info)
  - encoding format information (cdk::Format_info)
  - additional column information (cdk::Column_info)

  The first two items describe the type and encoding of the values that appear
  in the result in the corresponding column. Additional column information
  is mainly the column name etc.

  Classes Format_descr<TTT> and Format_info are used to store type and encoding
  format information for each column. For CDK type TTT, class Format_descr<TTT>
  stores encoding format descriptor (instance of cdk::Format<TTT> class) and
  encoder/decoder for the values (instance of cdk::Codec<TTT> class)
  (Note: for some types TTT there is no codec or no format descriptor). Class
  Format_info is a variant class that can store Format_descr<TTT> values
  for different TTT.

  Class Column_info extends Format_info with additional storage for
  the cdk::Column_info data (column name etc).

  Using meta-data to decode result values
  ---------------------------------------

  Meta-data information stored in m_mdata member of Result_impl_base class
  is used to interpret raw bytes returned by CDK in the reply to a query.
  This interpretation is done by Row_impl class which takes (shared pointer
  to) Meta_data instance as its constructor parameter and stores it in its
  m_mdata member. Encoding format information for a given column is extracted
  from this Meta_data instance in Row_impl::get() which then passes it
  to Row_impl::conver_at() and eventually to the static Value::Access::mk()
  functions which construct value from raw bytes using the format encoding
  information.

  Row_impl is actually a template parametrized by the exact VAL class used
  to decode and store data received from the server. Normally this should be
  the common::Value class, but a specialization can be used which handles
  either data conversion or data storage in some special way. For example,
  X DevAPI uses a specialization to handle storage of structured data such
  as documents and arrays.

  The common::Value::Access::mk() functions are defined in terms of convert()
  function overloads defined in result.cc. They look at the encoding format
  information and use the encoder instance inside Format_descr<> object to
  convert raw bytes into a value of appropriate type.
*/


/*
  Encapsulates CDK encoding format information and a raw bytes decoder
  for values of given CDK type T. For some types either the decoder or the
  format information are redundant and thus not stored inside Format_descr<T>.
*/

template <cdk::Type_info T>
class Format_descr
{
public:

  cdk::Format<T> m_format;
  cdk::Codec<T>  m_codec;

  Format_descr(const cdk::Format_info &fi)
    : m_format(fi), m_codec(fi)
  {}
};


/*
  Format_descr<T> specializations for different types.
*/


template <>
struct Format_descr<cdk::TYPE_DOCUMENT>
{
  cdk::Format<cdk::TYPE_DOCUMENT> m_format;
  cdk::Codec<cdk::TYPE_DOCUMENT>  m_codec;

  Format_descr(const cdk::Format_info &fi)
    : m_format(fi)
  {}
};


/*
  Note: we do not decode temporal values yet, thus there is
  no codec in Format_descr class.
*/

template<>
struct Format_descr<cdk::TYPE_DATETIME>
{
  cdk::Format<cdk::TYPE_DATETIME> m_format;

  Format_descr(const cdk::Format_info &fi)
    : m_format(fi)
  {}
};


template <>
struct Format_descr<cdk::TYPE_BYTES>
{
  cdk::Format<cdk::TYPE_BYTES> m_format;

  Format_descr(const cdk::Format_info &fi)
    : m_format(fi)
  {}
};


/*
  Note: For GEOMETRY and XML types we do not decode the values.
  Also, CDK does not provide any encoding format information -
  GEOMETRY uses some unspecified MySQL internal representation
  format and XML format is well known.
*/

template<>
struct Format_descr<cdk::TYPE_GEOMETRY>
{
  Format_descr(const cdk::Format_info &)
  {}
};

template<>
struct Format_descr<cdk::TYPE_XML>
{
  Format_descr(const cdk::Format_info &)
  {}
};


/*
  Structure Format_info holds information about the type
  of a column (m_type) and about its encoding format in
  Format_descr<T> structure. Since C++ type of Format_descr<T>
  is different for each T, a variant object is used to store
  the appropriate Format_descr<T> value.
*/

struct Format_info
{
  typedef variant <
    Format_descr<cdk::TYPE_STRING>,
    Format_descr<cdk::TYPE_INTEGER>,
    Format_descr<cdk::TYPE_FLOAT>,
    Format_descr<cdk::TYPE_DOCUMENT>,
    Format_descr<cdk::TYPE_BYTES>,
    Format_descr<cdk::TYPE_DATETIME>,
    Format_descr<cdk::TYPE_GEOMETRY>,
    Format_descr<cdk::TYPE_XML>
  > Format_info_storage;

  cdk::Type_info      m_type;
  Format_info_storage m_fmt;

  template <cdk::Type_info T>
  explicit Format_info(const Format_descr<T> &fd)
    : m_type(T), m_fmt(fd)
  {}

  explicit
  Format_info(cdk::Type_info type, const Format_descr<cdk::TYPE_BYTES> &fmt)
    : m_type(type), m_fmt(fmt)
  {}

  template <cdk::Type_info T>
  Format_descr<T>& get() const
  {
    /*
      Note: we cast away constness here, because using a codec can
      modify it, and thus the Format_descr<T> must be mutable.
    */
    return const_cast<Format_descr<T>&>(
      m_fmt.get<Format_descr<T>>()
    );
  }

};


}}  // impl::common


MYSQLX_ABI_BEGIN(2,0)
namespace common {

using impl::common::Format_info;
using impl::common::Format_descr;


/*
  Storage for single column meta-data.

  This extends Fromat_info with members used to store other column meta-data
  such as its name etc.

  Note: All textual data is stored as utf8 encoded strings.

  Note: Column_info must be defined inside ABI namespace to preserve ABI
  compatibility (name of this class is used in public API)
*/

class Column_info
  : public Format_info
{
public:

  using string = std::string;

  string m_name;
  string m_label;
  string m_table_name;
  string m_table_label;
  string m_schema_name;
  string m_catalog;

  unsigned long  m_length;
  unsigned short m_decimals;
  cdk::collation_id_t m_collation;
  bool           m_padded = false;

  Column_info(const Column_info&) = default;

  /*
    Create Column_info instance for a column of type T that uses given
    encoding format. The rest of column meta-data should be filled in using
    store_info() method.
  */

  template <cdk::Type_info T>
  explicit Column_info(const Format_descr<T> &fmt)
    : Format_info(fmt)
  {}

  /*
    Create Clumn_info instance for a column which stores values of the given
    type as raw bytes. The fi parameter describes the raw bytes storage
    characteristics such as padding.
  */

  explicit Column_info(cdk::Type_info type, const cdk::Format_info &fi)
    : Format_info(type, Format_descr<cdk::TYPE_BYTES>(fi))
  {}

  /*
    After creating Column_info instance this method should be called to
    store information taken from cdk::Column_info interface.
  */

  void store_info(const cdk::Column_info &ci)
  {
    m_name = ci.orig_name();
    m_label = ci.name();

    if (ci.table())
    {
      m_table_name = ci.table()->orig_name();
      m_table_label = ci.table()->name();

      if (ci.table()->schema())
      {
        m_schema_name = ci.table()->schema()->name();
        if (ci.table()->schema()->catalog())
          m_catalog = ci.table()->schema()->catalog()->name();
      }
    }

    m_collation = ci.collation();
    m_length = ci.length();
    ASSERT_NUM_LIMITS(short unsigned, ci.decimals());
    m_decimals = static_cast<short unsigned>(ci.decimals());

    if (cdk::TYPE_BYTES == m_type)
    {
      uint64_t pad_width = get<cdk::TYPE_BYTES>().m_format.pad_width();
      if (0 < pad_width)
      {
        m_padded = true;
        assert(m_length == pad_width);
      }
    }
  }

};

}  // common
MYSQLX_ABI_END(2,0)


namespace impl {
namespace common {

using MYSQLX_ABI(2,0)::common::Column_info;


/*
  Meta_data<STR> holds type and format information for all columns in
  a result. This information is stored in Column_info<STR> objects. The
  string type STR is used to internally store textual meta-data information.
*/

struct Meta_data
{
protected:

  cdk::col_count_t  m_col_count = 0;
  std::map<cdk::col_count_t, Column_info> m_cols;

public:

  /*
    Create Meta_data instance and fill it using meta-data information
    read from the cdk::Meta_data interface.
  */

  Meta_data(cdk::Meta_data&);

  virtual ~Meta_data() {}


  col_count_t col_count() const { return m_col_count; }

  /*
    Return meta-data information for the column at the given position.
  */

  const Column_info& get_column(cdk::col_count_t pos) const
  {
    return m_cols.at(pos);
  }

  const Format_info& get_format(cdk::col_count_t pos) const
  {
    return m_cols.at(pos);
  }

  cdk::Type_info get_type(cdk::col_count_t pos) const
  {
    return get_format(pos).m_type;
  }

private:

  /*
    Add to this Meta_data instance information about column
    at position `pos`. The type and format information is given
    by cdk::Format_info object, additional column meta-data by
    cdk::Column_info object.
  */

  template<cdk::Type_info T>
  void add(
    cdk::col_count_t pos,
    const cdk::Column_info &ci,
    const cdk::Format_info &fi
  )
  {
    m_cols.emplace(pos, Column_info(Format_descr<T>(fi)));
    m_cols.at(pos).store_info(ci);
  }

  /*
    Add raw column information (whose values are presented as
    raw bytes).
  */

  void add_raw(
    cdk::col_count_t pos,
    const cdk::Column_info &ci,
    cdk::Type_info type,
    const cdk::Format_info &fi
  )
  {
    m_cols.emplace(pos, Column_info(type, fi));
    m_cols.at(pos).store_info(ci);
  }

};

using Shared_meta_data = std::shared_ptr<Meta_data>;


/*
  Create Meta_data instance using information provided by
  cdk::Meta_data interface.

  This costructor calls appropriate add() methods to add Column_info
  instances to the m_cols map.
*/

inline
Meta_data::Meta_data(cdk::Meta_data &md)
{
  m_col_count = md.col_count();

  for (col_count_t pos = 0; pos < m_col_count; ++pos)
  {
    cdk::Type_info ti = md.type(pos);
    const cdk::Format_info &fi = md.format(pos);
    const cdk::Column_info &ci = md.col_info(pos);

    switch (ti)
    {
    case cdk::TYPE_STRING:    add<cdk::TYPE_STRING>(pos, ci, fi);   break;
    case cdk::TYPE_INTEGER:   add<cdk::TYPE_INTEGER>(pos, ci, fi);  break;
    case cdk::TYPE_FLOAT:     add<cdk::TYPE_FLOAT>(pos, ci, fi);    break;
    case cdk::TYPE_DOCUMENT:  add<cdk::TYPE_DOCUMENT>(pos, ci, fi); break;
    case cdk::TYPE_DATETIME:  add<cdk::TYPE_DATETIME>(pos, ci, fi); break;
    case cdk::TYPE_GEOMETRY:  add<cdk::TYPE_GEOMETRY>(pos, ci, fi); break;
    case cdk::TYPE_XML:       add<cdk::TYPE_XML>(pos, ci, fi); break;
    default:
      add_raw(pos, ci, ti, fi);
      break;
    }
  }
}


/*
  Handling result data
  ====================
*/


/*
  Convenience wrapper around std container that is used
  to store incoming raw bytes sequence.

  TODO: More efficient implementation.
*/

class Buffer
{
  std::vector<byte> m_impl;

public:

  void append(cdk::bytes data)
  {
    m_impl.insert(m_impl.end(), data.begin(), data.end());
  }

  size_t size() const { return m_impl.size(); }

  cdk::bytes data() const
  {
    return cdk::bytes((byte*)m_impl.data(), m_impl.size());
  }
};


/*
  Data structure used to hold raw row data. It holds a Buffer with
  raw bytes for each non-null field of a row.
*/

typedef std::map<col_count_t, Buffer> Row_data;


/*
  Implementation for a single Row instance. It holds a copy of
  raw data and a shared pointer to row set meta-data.

  It is possible to create an empty Row_impl instance and populate it using
  set() method. Such Row_impl instance does not correspond to a row received
  from the server, but represents a row that is defined by the user (for
  example, to be later inserted into a table).

  This template is parametrized by VAL class, such as commmon::Value used
  to convert and store result data. Converted values are stored as instances
  of VAL class in m_vals map and method get() returns references to these
  instances.

  Note: VAL class must define static method template used for converting raw
  bytes into values:

    template <cdk::Type_info T>
    Value Value::Access::mk(cdk::bytes data, mysqlx::Format_descr<T>&)
*/

template <class VAL = Value>
class Row_impl
{
public:

  using Value = VAL;
  using bytes = cdk::bytes;

  Row_impl() {};

  // Note: row data is copied into Row_impl instance

  Row_impl(const Row_data &data, const Shared_meta_data &md)
    : m_data(data), m_mdata(md)
  {}

protected:

  Row_data          m_data;
  Shared_meta_data  m_mdata;
  std::map<col_count_t, Value>    m_vals;
  col_count_t                     m_col_count = 0;

public:

  void clear()
  {
    m_data.clear();
    m_vals.clear();
    m_mdata.reset();
  }

  col_count_t col_count() const
  {
    return m_mdata ? m_mdata->col_count() : m_col_count;
  }

  bytes get_bytes(col_count_t pos) const
  {
    if (m_mdata && pos >= m_mdata->col_count())
      throw std::out_of_range("row column");

    auto data = m_data.find(pos);

    // Note: no data at given pos means null value.
    return data != m_data.end() ? data->second.data() : bytes{};
  }

  /*
    Get value of field at given position after converting to Value.
    @throws std::out_of_range if given column does not exist in the row.
  */

  Value& get(col_count_t pos)
  {
    if (m_mdata && pos >= m_mdata->col_count())
      throw std::out_of_range("row column");

    auto val = m_vals.find(pos);

    if (val != m_vals.end())
      return val->second;

    if (!m_mdata)
      throw std::out_of_range("no meta-data found");

    const Format_info &fi = m_mdata->get_format(pos);
    convert_at(pos, fi);
    return m_vals.at(pos);
  }

  void set(col_count_t pos, const Value &val)
  {
    m_vals.emplace(pos, val);
    if (pos >= m_col_count)
      m_col_count = pos + 1;
  }

private:

  void convert_at(col_count_t pos, const Format_info &fi)
  {
    Buffer *raw = nullptr;

    auto data = m_data.find(pos);

    if (data != m_data.end())
      raw = &(data->second);

    if (!raw || 0 == raw->size())
    {
      // Null value
      m_vals.emplace(pos, Value());
      return;
    }

    /*
      Call static function VAL::Access:mk() to construct VAL instance from
      raw bytes and put it into m_vals map. Aprropriate encoding format
      information is extracted from fi.
    */

#define CONVERT(T) case cdk::TYPE_##T: \
    m_vals.emplace(pos, \
      VAL::Access::mk(raw->data(), fi.get<cdk::TYPE_##T>()) \
    ); \
    break;

    switch (fi.m_type)
    {
      CDK_TYPE_LIST(CONVERT)
    }
  }

};


/*
  Given encoding format information, convert raw bytes to the corresponding
  value.
*/

Value convert(cdk::bytes, Format_descr<cdk::TYPE_STRING>&);
Value convert(cdk::bytes, Format_descr<cdk::TYPE_INTEGER>&);
Value convert(cdk::bytes, Format_descr<cdk::TYPE_FLOAT>&);
Value convert(cdk::bytes, Format_descr<cdk::TYPE_DOCUMENT>&);
Value convert(cdk::bytes, Format_descr<cdk::TYPE_DATETIME>&);

/*
  Generic template used when no type-specific specialization is defined.
  It builds a value holding the raw bytes.
*/

template <cdk::Type_info T>
Value convert(cdk::bytes data, Format_descr<T>&)
{
  /*
    Note: Trailing '\0' byte is used for NULL value detection and is not
    part of the data
  */
  return{ data.begin(), data.size() - 1 };
}

}}  // impl::common


MYSQLX_ABI_BEGIN(2, 0)
namespace common {

/*
  This static function is called by Row_impl to build column value from raw
  bytes.
*/

template<cdk::Type_info T>
inline
Value Value::Access::mk(cdk::bytes data, impl::common::Format_descr<T> &format)
{
  // Use convert() to convert raw bytes into a value

  Value val{ convert(data, format) };

  /*
    Store raw representation in m_str if possible (for RAW value this is
    already done by the constructor).
  */

  switch (val.get_type())
  {
  case Value::RAW:
  case Value::STRING:
  case Value::VNULL:
    break;

  default:
    /*
      Note: Trailing '\0' byte is used for NULL value detection and is not
      part of the data
    */
    val.m_str.assign(data.begin(), data.end() - 1);
    break;
  }

  return val;
}

}  // common
MYSQLX_ABI_END(2, 0)


/*
  Implementation of result object
  ===============================

  Result object of class Result_impl<STR> gives access to a reply sent
  by the server in response to a query. It gives information about the reply
  such as whether it contains row data or affected items counts etc. If reply
  contains row data, result object stores and gives access to meta-data about
  this row data. It also handles reading rows from the reply. A result object
  can handle server replies which contain multiple results.

  Template parameter STR is a string class used for storing textual meta-data
  - the get_column() method presents column meta-data as Column_info<STR>
  instances.

  A result object is created from a Result_init instance which contains
  information about the session and CDK reply object used to access server
  reply. Method Executable_if::execute() implemented by objects representing
  queries returns such a Result_init object that is then used to initialize
  a result instance.
*/

MYSQLX_ABI_BEGIN(2,0)
namespace common {

using impl::common::Shared_meta_data;
using impl::common::Row_data;
using impl::common::Column_info;

/*
  An abstract interface used to initialize result of an operation.

  An object implementing this interface can be used to construct a result
  object (see ctor of Result_impl_base).

  Note: This class must be defined inside ABI namespace to preserve ABI
  compatibility (its name is used in public API)
*/

class Result_init
{
public:

  virtual Shared_session_impl get_session() = 0;

  /*
    Return pointer to the cdk reply object which gives access to results
    returned by the server. The caller of this method takes ownership of
    the reply object.
  */

  virtual cdk::Reply*      get_reply() = 0;

  /*
    A hook that can perform additional initialization of the result object
    being constructed from a Result_init instance.
  */

  virtual void init_result(Result_impl&) {} // GCOV_EXCL_LINE
};


/*
  Base class for Result_impl<STR> with all members that are not dependent
  on the STR template parameter.

  Given a server reply to a command, it processes the reply giving access to
  the result data and meta-data.

  Note: This class must be defined inside ABI namespace to preserve ABI
  compatibility (its name is used in public API)
*/

class Result_impl
  : public cdk::Row_processor
  , public cdk::api::Diagnostics
{
public:

  Result_impl(Result_init &init);

  virtual ~Result_impl();

  /*
    Prepare for reading (the next) result.

    This method should be called first, before any other methods which access
    the result. After consuming one result, it should be called again to see
    if more results are pending (which can happen in case of a multi-result
    reply from the server).

    Returns true if (the next) result is ready for reading. Otherwise there
    are no more results and server reply has been entirely consumed. Note that
    the result does not need to contain row data - this can be determined by
    has_data() method.
  */

  bool next_result();

  /*
    Returns true if the current result has (more) rows to be fetched.
  */

  bool has_data() const;

  /*
    Return reference to result meta-data. Note that returned meta-data can
    be used together with Row_data from get_row() to build a Row_impl
    instance.
  */

  const Shared_meta_data& get_mdata() const;

  /*
    Fetches next row from the result, if any. Returns NULL if there are no
    more rows. Throws exception if this result has no data.

    Note: Rows are cached internally and read in larger batches.
  */

  const Row_data *get_row();

  // Store all remaining rows in the internal cache.

  void store();

  // Store all results to cache

  void store_all_results();

  /*
    Return the number of rows remaining in the result (the rows that have been
    already fetched with get_row() are not counted).

    This has a side effect of storing all rows in the chache.
  */

  row_count_t count();

  /*
    Discard the reply. TODO: Implement it when needed.
  */
  //void discard();

  /*
    Methods to access result information
  */

  col_count_t get_col_count() const;
  cdk::row_count_t get_affected_rows() const;
  cdk::row_count_t get_auto_increment() const;
  unsigned get_warning_count() const;

  /*
    Client-side filtering of row data. Function m_row_filter is applied
    for each received row to determine if it should be skipped.
  */

  using Row_filter_t = std::function<bool(const Row_data&)>;
  Row_filter_t m_row_filter = [](const Row_data&) { return true; };

  // Get generated document id information.

  const std::vector<std::string>& get_generated_ids() const;

  /*
    Get column information.

    Returns reference to Column_info<STR> object with information about
    the column. This Column_info<STR> object sits inside and is owned by
    this Result_impl object.
  */

  const Column_info& get_column(col_count_t pos) const
  {
    if (m_result_mdata.empty() || !m_result_mdata.front())
      THROW("No result set");
    return m_result_mdata.front()->get_column(pos);
  }

protected:

  Result_impl(const Result_impl&) = delete;


  /*
    Note: Session implementation must exists as long as this result
    implementation exists. Thus we keep shared pointer to it to make sure
    that session implementation is not destroyed when session object itself
    is deleted.
  */

  Shared_session_impl  m_sess;


  // -- Result meta-data

  /*
    This flag is set to true after next_result(), when the result has been
    prepared.
  */

  bool m_inited = false;

  // Note: meta-data can be shared with Row instances

  std::queue<Shared_meta_data>  m_result_mdata;

  /*
    Method fetch_meta_data() creates a new Meta_data_base instance with
    the information taken from a CDK cursor object (or any other object
    implementing cdk::Meta_data interface). This is called from next_result()
    when meta-data is available. The returned object is stored in m_mdata which
    takes its ownership.
  */

  //Meta_data_base* fetch_meta_data(cdk::Meta_data&) = 0;


  // -- Result data

  /*
    This flag is true if there are pending rows that server sends and that
    should be consumed.
  */

  bool m_pending_rows = false;

  /*
    Note: Not using auto-ptrs for m_reply and m_cursor to ensure correct
    order of deleting them (see dtor).
  */

  cdk::Reply  *m_reply;
  cdk::Cursor *m_cursor = nullptr;

  using Row_cache = std::forward_list<Row_data>;

  // Each queue elements represents a resultset.

  std::queue<Row_cache> m_result_cache;
  std::queue<row_count_t> m_result_cache_size;
  Row_cache::iterator m_cache_it;

  /*
    Ensure some rows are loaded into the cache. If cache is not empty, it
    returns true right away. Otherwise it loads rows into the cache. If
    preftetch_size is non-zero then at most that many rows are loaded.

    Returns true if some rows are present in the cache.

    This will cache the current resultset to the back of the queue
  */

  bool load_cache(row_count_t prefetch_size = 0);

  // Jumps to new resultset without poping the cache element

  bool read_next_result();

  // Called after resultset has been get by user

  void pop_row_cache()
  {
    auto lock = m_sess->lock();
    if(!m_result_mdata.empty())
      m_result_mdata.pop();
    if (!m_result_cache.empty())
    {
      /*
        At this point m_cache_it can point inside the last entry of
        m_result_cache that is just to be removed. Such dangling iterator
        can cause issues when later it is assigned to a new value (as
        compiler thinks it is still pointing inside old container and might
        want to do some cleanups). To avoid dangling iterator, we reset it
        to something neutral here.

        Note: This "fix" works under assumption that "one past the end"
        iterator is compatible between different containers, which seems to
        be the case for all compilers we use.

        TODO: A better solution would be to use std::option<> type for
        m_cache_it.
      */

      m_cache_it = m_result_cache.back().end();
      m_result_cache.pop();
    }
    if (!m_result_cache_size.empty())
      m_result_cache_size.pop();
  }

  // Called on each resultset to be read.

  void push_row_cache();

public:

  // -- Diagnostic information

  // Return number of diagnostic entries with given error level (defaults to ERROR).
  unsigned int entry_count(Severity::value level = Severity::ERROR) override
  {
    auto lock = m_sess->lock();
    if (!m_reply)
      THROW("Attempt to get warning count for empty result");

    return m_reply->entry_count(level);
  }

  // Get an iterator to iterate over diagnostic entries with level above or equal to given one
  // (for example, if level is WARNING then iterates over all warnings and errors).
  // By default returns iterator over errors only. The Error_iterator interface extends
  // Iterator interface with single Error_iterator::error() method that returns the current error entry from the sequence.
  Iterator& get_entries(Severity::value level = Severity::ERROR) override
  {
    auto lock = m_sess->lock();
    if (!m_reply)
      THROW("Attempt to get warning count for empty result");
    return m_reply->get_entries(level);
  }

  // Convenience method to return first error entry (if any).
  // Equivalent to get_erros().error(). Note that this method can throw exception if there is no error available.
  const cdk::Error& get_error() override
  {
    auto lock = m_sess->lock();
    if (!m_reply)
      THROW("Attempt to get warning count for empty result");
    return m_reply->get_error();
  }

private:

  // Row_processor

  Row_data    m_row;

  bool row_begin(row_count_t) override
  {
    m_row.clear();
    return true;
  }

  void row_end(row_count_t) override;

  size_t field_begin(col_count_t pos, size_t) override;
  void   field_end(col_count_t) override {}
  void   field_null(col_count_t) override {}
  size_t field_data(col_count_t pos, bytes) override;
  void   end_of_data() override;

};


inline
col_count_t Result_impl::get_col_count() const
{
  auto lock = m_sess->lock();
  if (m_result_mdata.empty())
    THROW("No result set");
  return m_result_mdata.front()->col_count();
}

inline
cdk::row_count_t Result_impl::get_affected_rows() const
{
  auto lock = m_sess->lock();
  if (!m_reply)
    THROW("Attempt to get affected rows count on empty result");
  return m_reply->affected_rows();
}

inline
cdk::row_count_t Result_impl::get_auto_increment() const
{
  auto lock = m_sess->lock();
  if (!m_reply)
    THROW("Attempt to get auto increment value on empty result");
  return m_reply->last_insert_id();
}

inline
unsigned Result_impl::get_warning_count() const
{
  auto lock = m_sess->lock();
  auto self = const_cast<Result_impl*>(this);
  self->store_all_results();
  return self->entry_count(cdk::api::Severity::WARNING);
}

inline
const std::vector<std::string>& Result_impl::get_generated_ids() const
{
  auto lock = m_sess->lock();
  if (!m_reply)
    THROW("Attempt to get generated ids for empty result");
  return m_reply->generated_ids();
}

inline
bool Result_impl::has_data() const
{
  auto lock = m_sess->lock();
  return (!m_result_cache.empty() && !m_result_cache.front().empty()) || m_pending_rows;
}


inline
const Shared_meta_data& Result_impl::get_mdata() const
{
  auto lock = m_sess->lock();
  return m_result_mdata.front();
}


inline
void Result_impl::store()
{
  auto lock = m_sess->lock();
  load_cache();
}

inline
void Result_impl::store_all_results()
{
  auto lock = m_sess->lock();
  do{
  store();
  }while(read_next_result());
}

inline
row_count_t Result_impl::count()
{
  auto lock = m_sess->lock();
  store();
  if (entry_count() > 0)
    get_error().rethrow();
  row_count_t rc = 0;
  if(!m_result_cache_size.empty())
    rc = m_result_cache_size.front();
  return rc;
}


}  // common
MYSQLX_ABI_END(2,0)
}  // mysqlx

#endif
