/*
 * Copyright (c) 2014, 2021, 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 also distributed 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 included with MySQL.
 * 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
 */

#include "modules/devapi/mod_mysqlx_resultset.h"

#include <memory>
#include <utility>

#include "modules/devapi/base_constants.h"
#include "modules/mysqlxtest_utils.h"
#include "mysqlshdk/include/scripting/common.h"
#include "mysqlshdk/include/scripting/obj_date.h"
#include "mysqlshdk/include/scripting/type_info/custom.h"
#include "mysqlshdk/include/scripting/type_info/generic.h"
#include "mysqlshdk/include/shellcore/base_shell.h"
#include "mysqlshdk/include/shellcore/utils_help.h"
#include "mysqlshdk/libs/db/charset.h"
#include "mysqlshdk/libs/db/row_copy.h"
#include "mysqlshdk/libs/db/session.h"
#include "mysqlshdk/libs/utils/strformat.h"
#include "mysqlshdk/libs/utils/utils_json.h"

using shcore::Value;
using std::placeholders::_1;

namespace mysqlsh {
namespace mysqlx {

// -----------------------------------------------------------------------

// Documentation of BaseResult class
REGISTER_HELP_CLASS(BaseResult, mysqlx);
REGISTER_HELP(
    BASERESULT_BRIEF,
    "Base class for the different types of results returned by the server.");

BaseResult::BaseResult(std::shared_ptr<mysqlshdk::db::mysqlx::Result> result)
    : _result(result) {
  add_property("affectedItemsCount", "getAffectedItemsCount");
  add_property("executionTime", "getExecutionTime");
  add_property("warningCount", "getWarningCount");
  add_property("warningsCount", "getWarningsCount");
  add_property("warnings", "getWarnings");
}

BaseResult::~BaseResult() {}

// Documentation of getWarnings function
REGISTER_HELP_PROPERTY(warnings, BaseResult);
REGISTER_HELP(BASERESULT_WARNINGS_BRIEF, "Same as <<<getWarnings>>>");
REGISTER_HELP_FUNCTION(getWarnings, BaseResult);
REGISTER_HELP_FUNCTION_TEXT(BASERESULT_GETWARNINGS, R"*(
Retrieves the warnings generated by the executed operation.

@returns A list containing a warning object for each generated warning.

This is the same value than C API mysql_warning_count, see
https://dev.mysql.com/doc/refman/en/mysql-warning-count.html

Each warning object contains a key/value pair describing the information
related to a specific warning.

This information includes: Level, Code and Message.
)*");
/**
 * $(BASERESULT_GETWARNINGS_BRIEF)
 *
 * $(BASERESULT_GETWARNINGS)
 */
#if DOXYGEN_JS
List BaseResult::getWarnings() {}
#elif DOXYGEN_PY
list BaseResult::get_warnings() {}
#endif

shcore::Value BaseResult::get_member(const std::string &prop) const {
  shcore::Value ret_val;

  if (prop == "affectedItemsCount") {
    ret_val = Value(get_affected_items_count());
  } else if (prop == "executionTime") {
    return shcore::Value(get_execution_time());
  } else if (prop == "warningCount") {
    log_warning("'%s' is deprecated, use '%s' instead.",
                get_function_name("warningCount").c_str(),
                get_function_name("warningsCount").c_str());
    ret_val = Value(get_warnings_count());
  } else if (prop == "warningsCount") {
    ret_val = Value(get_warnings_count());
  } else if (prop == "warnings") {
    std::shared_ptr<shcore::Value::Array_type> array(
        new shcore::Value::Array_type);
    if (_result) {
      while (std::unique_ptr<mysqlshdk::db::Warning> warning =
                 _result->fetch_one_warning()) {
        mysqlsh::Row *warning_row = new mysqlsh::Row();
        switch (warning->level) {
          case mysqlshdk::db::Warning::Level::Note:
            warning_row->add_item("level", shcore::Value("Note"));
            break;
          case mysqlshdk::db::Warning::Level::Warn:
            warning_row->add_item("level", shcore::Value("Warning"));
            break;
          case mysqlshdk::db::Warning::Level::Error:
            warning_row->add_item("level", shcore::Value("Error"));
            break;
        }
        warning_row->add_item("code", shcore::Value(warning->code));
        warning_row->add_item("message", shcore::Value(warning->msg));

        array->push_back(shcore::Value::wrap(warning_row));
      }
    }
    ret_val = shcore::Value(array);
  } else {
    ret_val = ShellBaseResult::get_member(prop);
  }

  return ret_val;
}

bool BaseResult::has_data() const {
  return _result && _result->has_resultset();
}

// Documentation of getAffectedItemCount function
REGISTER_HELP_PROPERTY(affectedItemsCount, BaseResult);
REGISTER_HELP(BASERESULT_AFFECTEDITEMSCOUNT_BRIEF,
              "Same as <<<getAffectedItemsCount>>>");

REGISTER_HELP_FUNCTION(getAffectedItemsCount, BaseResult);
REGISTER_HELP_FUNCTION_TEXT(BASERESULT_GETAFFECTEDITEMSCOUNT, R"*(
The the number of affected items for the last operation.

@returns the number of affected items.

Returns the number of records affected by the executed operation.
)*");
/**
 * $(BASERESULT_GETAFFECTEDITEMSCOUNT_BRIEF)
 *
 * $(BASERESULT_GETAFFECTEDITEMSCOUNT)
 */
#if DOXYGEN_JS
Integer BaseResult::getAffectedItemsCount() {}
#elif DOXYGEN_PY
int BaseResult::get_affected_items_count() {}
#endif

int64_t BaseResult::get_affected_items_count() const {
  if (!_result) return -1;
  return _result->get_affected_row_count();
}

// Documentation of getExecutionTime function
REGISTER_HELP_PROPERTY(executionTime, BaseResult);
REGISTER_HELP(BASERESULT_EXECUTIONTIME_BRIEF, "Same as <<<getExecutionTime>>>");
REGISTER_HELP_FUNCTION(getExecutionTime, BaseResult);
REGISTER_HELP(BASERESULT_GETEXECUTIONTIME_BRIEF,
              "Retrieves a string value indicating the execution time of the "
              "executed operation.");

/**
 * $(BASERESULT_GETEXECUTIONTIME_BRIEF)
 */
#if DOXYGEN_JS
String BaseResult::getExecutionTime() {}
#elif DOXYGEN_PY
str BaseResult::get_execution_time() {}
#endif

std::string BaseResult::get_execution_time() const {
  return mysqlshdk::utils::format_seconds(_result->get_execution_time());
}

// Documentation of getWarningCount function
REGISTER_HELP_PROPERTY(warningCount, BaseResult);
REGISTER_HELP(BASERESULT_WARNINGCOUNT_BRIEF, "Same as <<<getWarningCount>>>");
REGISTER_HELP(BASERESULT_WARNINGCOUNT_DETAIL,
              "${BASERESULT_WARNINGCOUNT_DEPRECATED}");
REGISTER_HELP(BASERESULT_WARNINGCOUNT_DEPRECATED,
              "@attention This property will be removed in a future release, "
              "use the <b><<<warningsCount>>></b> property instead.");

REGISTER_HELP_FUNCTION(getWarningCount, BaseResult);
REGISTER_HELP_FUNCTION_TEXT(BASERESULT_GETWARNINGCOUNT, R"*(
The number of warnings produced by the last statement execution.

@returns the number of warnings.

@attention This function will be removed in a future release, use the
<b><<<getWarningsCount>>></b> function instead.

This is the same value than C API mysql_warning_count, see
https://dev.mysql.com/doc/refman/en/mysql-warning-count.html

See <<<getWarnings>>>() for more details.
)*");
/**
 * $(BASERESULT_GETWARNINGCOUNT_BRIEF)
 *
 * $(BASERESULT_GETWARNINGCOUNT)
 *
 * \sa warnings
 */
#if DOXYGEN_JS
Integer BaseResult::getWarningCount() {}
#elif DOXYGEN_PY
int BaseResult::get_warning_count() {}
#endif

// Documentation of getWarningCount function
REGISTER_HELP_PROPERTY(warningsCount, BaseResult);
REGISTER_HELP(BASERESULT_WARNINGSCOUNT_BRIEF, "Same as <<<getWarningsCount>>>");

REGISTER_HELP_FUNCTION(getWarningsCount, BaseResult);
REGISTER_HELP_FUNCTION_TEXT(BASERESULT_GETWARNINGSCOUNT, R"*(
The number of warnings produced by the last statement execution.

@returns the number of warnings.

This is the same value than C API mysql_warning_count, see
https://dev.mysql.com/doc/refman/en/mysql-warning-count.html

See <<<getWarnings>>>() for more details.
)*");
/**
 * $(BASERESULT_GETWARNINGSCOUNT_BRIEF)
 *
 * $(BASERESULT_GETWARNINGSCOUNT)
 *
 * \sa warnings
 */
#if DOXYGEN_JS
Integer BaseResult::getWarningsCount() {}
#elif DOXYGEN_PY
int BaseResult::get_warnings_count() {}
#endif

uint64_t BaseResult::get_warnings_count() const {
  if (_result) return _result->get_warning_count();
  return 0;
}

void BaseResult::append_json(shcore::JSON_dumper &dumper) const {
  bool create_object = (dumper.deep_level() == 0);

  if (create_object) dumper.start_object();

  dumper.append_value("executionTime", get_member("executionTime"));
  dumper.append_value("affectedItemsCount", get_member("affectedItemsCount"));

  if (mysqlsh::current_shell_options()->get().show_warnings) {
    dumper.append_value("warningCount", get_member("warningsCount"));
    dumper.append_value("warningsCount", get_member("warningsCount"));
    dumper.append_value("warnings", get_member("warnings"));
  }

  if (create_object) dumper.end_object();
}

// -----------------------------------------------------------------------

// Documentation of Result class
REGISTER_HELP_SUB_CLASS(Result, mysqlx, BaseResult);
REGISTER_HELP_CLASS_TEXT(RESULT, R"*(
Allows retrieving information about non query operations performed on the
database.

An instance of this class will be returned on the CRUD operations that change
the content of the database:

@li On Table: insert, update and delete
@li On Collection: add, modify and remove

Other functions on the Session class also return an instance of this class:

@li Transaction handling functions
)*");
Result::Result(std::shared_ptr<mysqlshdk::db::mysqlx::Result> result)
    : BaseResult(result) {
  add_property("affectedItemCount", "getAffectedItemCount");
  add_property("autoIncrementValue", "getAutoIncrementValue");
  add_property("generatedIds", "getGeneratedIds");
}

shcore::Value Result::get_member(const std::string &prop) const {
  Value ret_val;

  if (prop == "affectedItemCount") {
    ret_val = Value(get_affected_items_count());
    log_warning("'%s' is deprecated, use '%s' instead.",
                get_function_name("affectedItemCount").c_str(),
                get_function_name("affectedItemsCount").c_str());
  } else if (prop == "autoIncrementValue") {
    ret_val = Value(get_auto_increment_value());
  } else if (prop == "generatedIds") {
    auto array = shcore::make_array();

    for (auto &doc_id : get_generated_ids()) {
      array->emplace_back(std::move(doc_id));
    }

    ret_val = Value(std::move(array));
  } else {
    ret_val = BaseResult::get_member(prop);
  }

  return ret_val;
}

// Documentation of getAffectedItemCount function
REGISTER_HELP_PROPERTY(affectedItemCount, Result);
REGISTER_HELP(RESULT_AFFECTEDITEMCOUNT_BRIEF,
              "Same as <<<getAffectedItemCount>>>");
REGISTER_HELP(RESULT_AFFECTEDITEMCOUNT_DETAIL,
              "${RESULT_AFFECTEDITEMCOUNT_DEPRECATED}");
REGISTER_HELP(RESULT_AFFECTEDITEMCOUNT_DEPRECATED,
              "@attention This property will be removed in a future release, "
              "use the <b><<<affectedItemsCount>>></b> property instead.");

REGISTER_HELP_FUNCTION(getAffectedItemCount, Result);
REGISTER_HELP_FUNCTION_TEXT(RESULT_GETAFFECTEDITEMCOUNT, R"*(
The the number of affected items for the last operation.

@returns the number of affected items.

@attention This function will be removed in a future release, use the
<b><<<getAffectedItemsCount>>></b> function instead.

This is the value of the C API mysql_affected_rows(), see
https://dev.mysql.com/doc/refman/en/mysql-affected-rows.html
)*");
/**
 * $(RESULT_GETAFFECTEDITEMCOUNT_BRIEF)
 *
 * $(RESULT_GETAFFECTEDITEMCOUNT)
 */
#if DOXYGEN_JS
Integer Result::getAffectedItemCount() {}
#elif DOXYGEN_PY
int Result::get_affected_item_count() {}
#endif

// Documentation of getAutoIncrementValue function
REGISTER_HELP_PROPERTY(autoIncrementValue, Result);
REGISTER_HELP(RESULT_AUTOINCREMENTVALUE_BRIEF,
              "Same as <<<getAutoIncrementValue>>>");
REGISTER_HELP_FUNCTION(getAutoIncrementValue, Result);
REGISTER_HELP_FUNCTION_TEXT(RESULT_GETAUTOINCREMENTVALUE, R"*(
The last insert id auto generated (from an insert operation)

@returns the integer representing the last insert id

For more details, see 
https://dev.mysql.com/doc/refman/en/information-functions.html#function_last-insert-id

Note that this value will be available only when the result is for a
Table.insert operation.
)*");
/**
 * $(RESULT_GETAUTOINCREMENTVALUE_BRIEF)
 *
 * $(RESULT_GETAUTOINCREMENTVALUE)
 */
#if DOXYGEN_JS
Integer Result::getAutoIncrementValue() {}
#elif DOXYGEN_PY
int Result::get_auto_increment_value() {}
#endif

int64_t Result::get_auto_increment_value() const {
  if (_result) return _result->get_auto_increment_value();
  return 0;
}

// Documentation of getLastDocumentId function
REGISTER_HELP_PROPERTY(generatedIds, Result);
REGISTER_HELP(RESULT_GENERATEDIDS_BRIEF, "Same as <<<getGeneratedIds>>>.");
REGISTER_HELP_FUNCTION(getGeneratedIds, Result);
REGISTER_HELP_FUNCTION_TEXT(RESULT_GETGENERATEDIDS, R"*(
Returns the list of document ids generated on the server.

@returns a list of strings containing the generated ids.

When adding documents into a collection, it is required that an ID is
associated to the document, if a document is added without an '_id' field, an
error will be generated.

At MySQL 8.0.11 if the documents being added do not have an '_id' field, the
server will automatically generate an ID and assign it to the document.

This function returns a list of the IDs that were generated for the server to
satisfy this requirement.
)*");
/**
 * $(RESULT_GETGENERATEDIDS_BRIEF)
 *
 * $(RESULT_GETGENERATEDIDS)
 */
#if DOXYGEN_JS
List Result::getGeneratedIds() {}
#elif DOXYGEN_PY
list Result::get_generated_ids() {}
#endif
const std::vector<std::string> Result::get_generated_ids() const {
  if (_result)
    return _result->get_generated_ids();
  else
    return {};
}

void Result::append_json(shcore::JSON_dumper &dumper) const {
  dumper.start_object();

  BaseResult::append_json(dumper);

  dumper.append_value("affectedItemCount", get_member("affectedItemsCount"));
  dumper.append_value("autoIncrementValue", get_member("autoIncrementValue"));
  dumper.append_value("generatedIds", get_member("generatedIds"));

  dumper.end_object();
}

// -----------------------------------------------------------------------

// Documentation of DocResult class
REGISTER_HELP_SUB_CLASS(DocResult, mysqlx, BaseResult);
REGISTER_HELP(DOCRESULT_BRIEF,
              "Allows traversing the DbDoc objects returned by a "
              "Collection.find operation.");

DocResult::DocResult(std::shared_ptr<mysqlshdk::db::mysqlx::Result> result)
    : BaseResult(result) {
  expose("fetchOne", &DocResult::fetch_one);
  expose("fetchAll", &DocResult::fetch_all);
}

// Documentation of fetchOne function
REGISTER_HELP_FUNCTION(fetchOne, DocResult);
REGISTER_HELP_FUNCTION_TEXT(DOCRESULT_FETCHONE, R"*(
Retrieves the next DbDoc on the DocResult.

@returns A DbDoc object representing the next Document in the result.
)*");
/**
 * $(DOCRESULT_FETCHONE_BRIEF)
 *
 * $(DOCRESULT_FETCHONE)
 */
#if DOXYGEN_JS
Document DocResult::fetchOne() {}
#elif DOXYGEN_PY
Document DocResult::fetch_one() {}
#endif
shcore::Dictionary_t DocResult::fetch_one() const {
  shcore::Dictionary_t ret_val;

  if (_result) {
    if (const mysqlshdk::db::IRow *r = _result->fetch_one()) {
      ret_val = Value::parse(r->get_string(0)).as_map();
    }
  }

  return ret_val;
}

// Documentation of fetchAll function
REGISTER_HELP_FUNCTION(fetchAll, DocResult);
REGISTER_HELP_FUNCTION_TEXT(DOCRESULT_FETCHALL, R"*(
Returns a list of DbDoc objects which contains an element for every unread
document.

@returns A List of DbDoc objects.

If this function is called right after executing a query, it will return a
DbDoc for every document on the resultset.

If fetchOne is called before this function, when this function is called it
will return a DbDoc for each of the remaining documents on the resultset.
)*");
/**
 * $(DOCRESULT_FETCHALL_BRIEF)
 *
 * $(DOCRESULT_FETCHALL)
 */
#if DOXYGEN_JS
List DocResult::fetchAll() {}
#elif DOXYGEN_PY
list DocResult::fetch_all() {}
#endif
shcore::Array_t DocResult::fetch_all() const {
  auto array = shcore::make_array();

  // Gets the next document
  auto record = fetch_one();
  while (record) {
    array->push_back(shcore::Value(record));
    record = fetch_one();
  }

  return array;
}

void DocResult::append_json(shcore::JSON_dumper &dumper) const {
  dumper.start_object();

  dumper.append_value("documents", shcore::Value(fetch_all()));

  BaseResult::append_json(dumper);

  dumper.end_object();
}

// -----------------------------------------------------------------------

// Documentation of RowResult class
REGISTER_HELP_SUB_CLASS(RowResult, mysqlx, BaseResult);
REGISTER_HELP(
    ROWRESULT_BRIEF,
    "Allows traversing the Row objects returned by a Table.select operation.");

RowResult::RowResult(std::shared_ptr<mysqlshdk::db::mysqlx::Result> result)
    : BaseResult(result) {
  add_property("columnCount", "getColumnCount");
  add_property("columns", "getColumns");
  add_property("columnNames", "getColumnNames");

  expose("fetchOne", &RowResult::fetch_one);
  expose("fetchAll", &RowResult::fetch_all);
  expose("fetchOneObject", &RowResult::_fetch_one_object);
}

shcore::Value RowResult::get_member(const std::string &prop) const {
  Value ret_val;
  if (prop == "columnCount") {
    ret_val = shcore::Value(get_column_count());
  } else if (prop == "columnNames") {
    update_column_cache();
    auto array = shcore::make_array();

    if (m_column_names) {
      for (auto &column : *m_column_names) {
        array->push_back(shcore::Value(column));
      }
    }

    ret_val = shcore::Value(array);
  } else if (prop == "columns") {
    update_column_cache();

    if (m_columns) {
      ret_val = shcore::Value(m_columns);
    } else {
      ret_val = shcore::Value(shcore::make_array());
    }
  } else {
    ret_val = BaseResult::get_member(prop);
  }
  return ret_val;
}

// Documentation of getColumnCount function
REGISTER_HELP_PROPERTY(columnCount, RowResult);
REGISTER_HELP(ROWRESULT_COLUMNCOUNT_BRIEF, "Same as <<<getColumnCount>>>");
REGISTER_HELP_FUNCTION(getColumnCount, RowResult);
REGISTER_HELP_FUNCTION_TEXT(ROWRESULT_GETCOLUMNCOUNT, R"*(
Retrieves the number of columns on the current result.

@returns the number of columns on the current result.
)*");
/**
 * $(ROWRESULT_GETCOLUMNCOUNT_BRIEF)
 *
 * $(ROWRESULT_GETCOLUMNCOUNT)
 */
#if DOXYGEN_JS
Integer RowResult::getColumnCount() {}
#elif DOXYGEN_PY
int RowResult::get_column_count() {}
#endif
int64_t RowResult::get_column_count() const {
  return _result->get_metadata().size();
}

// Documentation of getColumnNames function
REGISTER_HELP_PROPERTY(columnNames, RowResult);
REGISTER_HELP(ROWRESULT_COLUMNNAMES_BRIEF, "Same as <<<getColumnNames>>>");
REGISTER_HELP_FUNCTION(getColumnNames, RowResult);
REGISTER_HELP_FUNCTION_TEXT(ROWRESULT_GETCOLUMNNAMES, R"*(
Gets the columns on the current result.

@returns A list with the names of the columns returned on the active result.
)*");
/**
 * $(ROWRESULT_GETCOLUMNNAMES_BRIEF)
 *
 * $(ROWRESULT_GETCOLUMNNAMES)
 */
#if DOXYGEN_JS
List RowResult::getColumnNames() {}
#elif DOXYGEN_PY
list RowResult::get_column_names() {}
#endif

// Documentation of getColumns function
REGISTER_HELP_PROPERTY(columns, RowResult);
REGISTER_HELP(ROWRESULT_COLUMNS_BRIEF, "Same as <<<getColumns>>>");
REGISTER_HELP_FUNCTION(getColumns, RowResult);
REGISTER_HELP_FUNCTION_TEXT(ROWRESULT_GETCOLUMNS, R"*(
Gets the column metadata for the columns on the active result.

@returns a list of Column objects containing information about the columns
included on the active result.
)*");
/**
 * $(ROWRESULT_GETCOLUMNS_BRIEF)
 *
 * $(ROWRESULT_GETCOLUMNS)
 */
#if DOXYGEN_JS
List RowResult::getColumns() {}
#elif DOXYGEN_PY
list RowResult::get_columns() {}
#endif

// Documentation of fetchOne function
REGISTER_HELP_FUNCTION(fetchOne, RowResult);
REGISTER_HELP_FUNCTION_TEXT(ROWRESULT_FETCHONE, R"*(
Retrieves the next Row on the RowResult.

@returns A Row object representing the next record on the result.
)*");
/**
 * $(ROWRESULT_FETCHONE_BRIEF)
 *
 * $(ROWRESULT_FETCHONE)
 */
#if DOXYGEN_JS
Row RowResult::fetchOne() {}
#elif DOXYGEN_PY
Row RowResult::fetch_one() {}
#endif
std::shared_ptr<mysqlsh::Row> RowResult::fetch_one() const {
  std::shared_ptr<mysqlsh::Row> ret_val;

  auto row = fetch_one_row();
  if (row) {
    ret_val = std::shared_ptr<mysqlsh::Row>(row.release());
  }

  return ret_val;
}

REGISTER_HELP_FUNCTION(fetchOneObject, RowResult);

REGISTER_HELP_FUNCTION_TEXT(ROWRESULT_FETCHONEOBJECT, R"*(
Retrieves the next Row on the result and returns it as an object.

@returns A dictionary containing the row information.

The column names will be used as keys in the returned dictionary and the column
data will be used as the key values.

If a column is a valid identifier it will be accessible as an object attribute
as @<dict@>.@<column@>.

If a column is not a valid identifier, it will be accessible as a dictionary
key as @<dict@>[@<column@>].
)*");
/**
 * $(ROWRESULT_FETCHONEOBJECT_BRIEF)
 *
 * $(ROWRESULT_FETCHONEOBJECT)
 */
#if DOXYGEN_JS
Dictionary RowResult::fetchOneObject() {}
#elif DOXYGEN_PY
dict RowResult::fetch_one_object() {}
#endif
shcore::Dictionary_t RowResult::_fetch_one_object() {
  return ShellBaseResult::fetch_one_object();
}

// Documentation of fetchAll function
REGISTER_HELP_FUNCTION(fetchAll, RowResult);
REGISTER_HELP_FUNCTION_TEXT(ROWRESULT_FETCHALL, R"*(
Returns a list of DbDoc objects which contains an element for every unread
document.

@returns A List of DbDoc objects.
)*");
/**
 * $(ROWRESULT_FETCHALL_BRIEF)
 *
 * $(ROWRESULT_FETCHALL)
 */
#if DOXYGEN_JS
List RowResult::fetchAll() {}
#elif DOXYGEN_PY
list RowResult::fetch_all() {}
#endif
shcore::Array_t RowResult::fetch_all() const {
  auto array = shcore::make_array();

  // Gets the next row
  auto record = fetch_one();
  while (record) {
    array->push_back(shcore::Value(record));
    record = fetch_one();
  }

  return array;
}

void RowResult::append_json(shcore::JSON_dumper &dumper) const {
  bool create_object = (dumper.deep_level() == 0);

  if (create_object) dumper.start_object();

  BaseResult::append_json(dumper);

  dumper.append_value("rows", shcore::Value(fetch_all()));

  if (create_object) dumper.end_object();
}

// Documentation of SqlResult class
REGISTER_HELP_SUB_CLASS(SqlResult, mysqlx, RowResult);
REGISTER_HELP(SQLRESULT_BRIEF,
              "Allows browsing through the result information after performing "
              "an operation on the database done through Session.sql");

SqlResult::SqlResult(std::shared_ptr<mysqlshdk::db::mysqlx::Result> result)
    : RowResult(result) {
  expose("hasData", &SqlResult::has_data);
  expose("nextDataSet", &SqlResult::next_data_set);
  expose("nextResult", &SqlResult::next_result);
  add_property("autoIncrementValue", "getAutoIncrementValue");
  add_property("affectedRowCount", "getAffectedRowCount");
}

// Documentation of getAutoIncrementValue function
REGISTER_HELP_PROPERTY(autoIncrementValue, SqlResult);
REGISTER_HELP(SQLRESULT_AUTOINCREMENTVALUE_BRIEF,
              "Same as <<<getAutoIncrementValue>>>");
REGISTER_HELP_FUNCTION(getAutoIncrementValue, SqlResult);
REGISTER_HELP_FUNCTION_TEXT(SQLRESULT_GETAUTOINCREMENTVALUE, R"*(
Returns the identifier for the last record inserted.

Note that this value will only be set if the executed statement inserted a
record in the database and an ID was automatically generated.
)*");
/**
 * $(SQLRESULT_GETAUTOINCREMENTVALUE_BRIEF)
 *
 * $(SQLRESULT_GETAUTOINCREMENTVALUE)
 */
#if DOXYGEN_JS
Integer SqlResult::getAutoIncrementValue() {}
#elif DOXYGEN_PY
int SqlResult::get_auto_increment_value() {}
#endif
int64_t SqlResult::get_auto_increment_value() const {
  if (_result) return _result->get_auto_increment_value();
  return 0;
}

// Documentation of getAffectedRowCount function
REGISTER_HELP_PROPERTY(affectedRowCount, SqlResult);
REGISTER_HELP(SQLRESULT_AFFECTEDROWCOUNT_BRIEF,
              "Same as <<<getAffectedRowCount>>>");
REGISTER_HELP(SQLRESULT_AFFECTEDROWCOUNT_DETAIL,
              "${SQLRESULT_AFFECTEDROWCOUNT_DEPRECATED}");
REGISTER_HELP(SQLRESULT_AFFECTEDROWCOUNT_DEPRECATED,
              "@attention This property will be removed in a future release, "
              "use the <b><<<affectedItemsCount>>></b> property instead.");
REGISTER_HELP_FUNCTION(getAffectedRowCount, SqlResult);
REGISTER_HELP_FUNCTION_TEXT(SQLRESULT_GETAFFECTEDROWCOUNT, R"*(
Returns the number of rows affected by the executed query.

@attention This function will be removed in a future release, use the
<b><<<getAffectedItemsCount>>></b> function instead.
)*");
/**
 * $(SQLRESULT_GETAFFECTEDROWCOUNT_BRIEF)
 *
 * $(SQLRESULT_GETAFFECTEDROWCOUNT)
 */
#if DOXYGEN_JS
Integer SqlResult::getAffectedRowCount() {}
#elif DOXYGEN_PY
int SqlResult::get_affected_row_count() {}
#endif
int64_t SqlResult::get_affected_row_count() const {
  if (_result) return _result->get_affected_row_count();
  return 0;
}

shcore::Value SqlResult::get_member(const std::string &prop) const {
  Value ret_val;
  if (prop == "autoIncrementValue") {
    ret_val = Value(get_auto_increment_value());
  } else if (prop == "affectedRowCount") {
    ret_val = Value(get_affected_row_count());
    log_warning("'%s' is deprecated, use '%s' instead.",
                get_function_name("affectedRowCount").c_str(),
                get_function_name("affectedItemsCount").c_str());
  } else {
    ret_val = RowResult::get_member(prop);
  }

  return ret_val;
}

// Documentation of hasData function
REGISTER_HELP_FUNCTION(hasData, SqlResult);
REGISTER_HELP(SQLRESULT_HASDATA_BRIEF,
              "Returns true if the last statement execution has a result set.");

/**
 * $(SQLRESULT_HASDATA_BRIEF)
 */
#if DOXYGEN_JS
Bool SqlResult::hasData() {}
#elif DOXYGEN_PY
bool SqlResult::has_data() {}
#endif

// Documentation of nextDataSet function
REGISTER_HELP_FUNCTION(nextDataSet, SqlResult);
REGISTER_HELP_FUNCTION_TEXT(SQLRESULT_NEXTDATASET, R"*(
Prepares the SqlResult to start reading data from the next Result (if many
results were returned).

@returns A boolean value indicating whether there is another result or not.

@attention This function will be removed in a future release, use the
<b><<<nextResult>>></b> function instead.
)*");
/**
 * $(SQLRESULT_NEXTDATASET_BRIEF)
 *
 * $(SQLRESULT_NEXTDATASET)
 */
#if DOXYGEN_JS
Bool SqlResult::nextDataSet() {}
#elif DOXYGEN_PY
bool SqlResult::next_data_set() {}
#endif
bool SqlResult::next_data_set() {
  log_warning("'%s' is deprecated, use '%s' instead.",
              get_function_name("nextDataSet").c_str(),
              get_function_name("nextResult").c_str());

  return next_result();
}

// Documentation of nextDataSet function
REGISTER_HELP_FUNCTION(nextResult, SqlResult);
REGISTER_HELP_FUNCTION_TEXT(SQLRESULT_NEXTRESULT, R"*(
Prepares the SqlResult to start reading data from the next Result (if many
results were returned).

@returns A boolean value indicating whether there is another result or not.
)*");
/**
 * $(SQLRESULT_NEXTRESULT_BRIEF)
 *
 * $(SQLRESULT_NEXTRESULT)
 */
#if DOXYGEN_JS
Bool SqlResult::nextResult() {}
#elif DOXYGEN_PY
bool SqlResult::next_result() {}
#endif
bool SqlResult::next_result() {
  reset_column_cache();
  return _result->next_resultset();
}

void SqlResult::append_json(shcore::JSON_dumper &dumper) const {
  dumper.start_object();

  RowResult::append_json(dumper);

  dumper.append_value("hasData", shcore::Value(has_data()));
  dumper.append_value("affectedRowCount", get_member("affectedItemsCount"));
  dumper.append_value("autoIncrementValue", get_member("autoIncrementValue"));

  dumper.end_object();
}

}  // namespace mysqlx
}  // namespace mysqlsh
