/*
   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
*/

#ifndef MYSQL_QUERY_RUNNER_INCLUDED
#define MYSQL_QUERY_RUNNER_INCLUDED

#include "my_global.h"
#include "mysql.h"
#include "mutex.h"
#include "i_callable.h"
#include "message_data.h"
#include "atomic.h"
#include <algorithm>
#include <string>
#include <vector>

namespace Mysql{
namespace Tools{
namespace Base{

/**
  Helper class to run SQL query on existing MySQL database server connection,
  receive all data and all errors, warnings and notes returned during query
  execution. All acquired information is passed to set of callbacks to make
  data flows more customizable.
 */
class Mysql_query_runner
{
public:
  class Row;

  /**
    Standard constructor based on MySQL connection.
   */
  Mysql_query_runner(MYSQL* connection);
  /**
    Copy constructor.
   */
  Mysql_query_runner(const Mysql_query_runner& source);

  ~Mysql_query_runner();
  /**
    Adds new callback to be called on every result row of query.
    If callback return value other than 0 then query execution, passing
    current row to other callbacks and error messages processing, and
    Mysql_query_runner::run_query() will return value returned from this
    callback.
    Callbacks are called in reverse order of addition, i.e. newest are first.
   */
  Mysql_query_runner& add_result_callback(
    I_callable<int64, const Row&>* result_callback);
  /**
    Adds new callback to be called on every message after query execution,
    this includes errors, warnings and other notes. Return value from callback
    of 0 will lead to next handler being called, positive number return value
    will cause Mysql_query_runner::run_query() will return immediately this
    value and negative number will continue query execution and other messages
    processing, but will not pass current message to rest of callbacks.
    Callbacks are called in reverse order of addition, i.e. newest are first.
   */
  Mysql_query_runner& add_message_callback(
    I_callable<int64, const Message_data&>* message_callback);
  /**
    Runs specified query and processes result rows and messages to callbacks.
   */
  int64 run_query(std::string query);
  /**
    Runs specified query, fills result vector with processed result rows
    and processes messages to callbacks.
   */
  int64 run_query_store(
    std::string query, std::vector<const Row*>* result);
  /**
    Runs specified query with result callback specified. Does not add specified
    callback to list of callbacks, next queries will not process rows to this
    callback.
   */
  int64 run_query(
    std::string query,
    I_callable<int64, const Row&>* result_callback);
  /**
    Returns escaped copy of string to use in queries.
   */
  std::string escape_string(const std::string& original);
  /**
    Escapes specified input string and appends it escaped to destination
    string.
   */
  void append_escape_string(std::string* destination_string,
    const std::string& original);
  /**
    Escapes specified input string specified as characters buffer and its size,
    and appends it escaped to destination string.
   */
  void append_escape_string(std::string* destination_string,
    const char* original, size_t original_length);
  /**
    Converts to HEX specified input string specified as characters buffer and
    its size, and appends it escaped to destination string.
   */
  void append_hex_string(std::string* destination_string,
    const char* original, size_t original_length);

  /**
    Empties memory used by result strings.
   */
  static void cleanup_result(const Row& result);
  /**
    Empties memory used by result strings.
   */
  static void cleanup_result(std::vector<const Row*>* result);

  MYSQL* get_low_level_connection() const;

  class Row
  {
  public:
    class Iterator;

    Row(MYSQL_RES* mysql_result_info, unsigned int column_count,
        MYSQL_ROW row);
    ~Row();
    std::string operator[] (std::size_t index) const;
    void push_back(char* buff, std::size_t length);
    const char* get_buffer(std::size_t index, std::size_t& length) const;
    std::size_t size_of_element(std::size_t index) const;
    bool is_value_null(std::size_t index) const;
    std::size_t size() const;
    Iterator begin() const;
    Iterator end() const;
    MYSQL_RES* get_mysql_result_info() const;

    class Iterator
    {
    public:
      Iterator(const Row& row, std::size_t index);
      bool is_value_null();
      std::string operator *();
      void operator ++();
      bool operator ==(const Iterator& other);
      bool operator !=(const Iterator& other);

    private:
      const Row& m_row;
      std::size_t m_index;
    };

  private:
    void reserve(std::size_t strings, std::size_t buffer_size);

    // Represents table row as a string
    char* m_buffer;
    // Represents offsets to each column in m_buffer
    std::vector<std::size_t> m_buffer_starts;
    // Total buffer size
    std::size_t m_buffer_capacity;
    // Actual buffer size
    std::size_t m_buffer_size;
    MYSQL_RES* m_mysql_result_info;
  };

private:
  /**
    Runs specified query and process result rows and messages to callbacks.
    Does not check for multiple queries being executed in parallel.
  */
  int64 run_query_unguarded(std::string query);
  /**
    Creates error message from mysql_errno and mysql_error and passes it to
    callbacks.
   */
  int64 report_mysql_error();
  /**
    Creates error message from mysql_errno and mysql_error and passes it to
    callbacks.
   */
  int64 report_message(Message_data &message);
  /**
    Returns parsed Message_type from given MySQL severity string.
   */
  Message_type get_message_type_from_severity(std::string severity);

  class Store_result_helper
  {
  public:
    Store_result_helper(std::vector<const Row*>* result);
    I_callable<int64, const Row&>* get_result_callback();

  private:
    int64 result_callback(const Row& row);

    std::vector<const Row*>* m_result;
  };

  std::vector<I_callable<int64, const Row&>*>
    m_result_callbacks;
  std::vector<I_callable<int64, const Message_data&>*> m_message_callbacks;

  /**
    Indicates if there is query currently executed. Only one query can be
    executed in specified time moment.
   */
  my_boost::atomic<bool>* m_is_processing;

  /**
    Indicates if this is original runner or a copy. In case of original the
    cleanup is performed on destruction.
  */
  bool m_is_original_runner;

  MYSQL* m_connection;
};

}
}
}

#endif
