/*
   Copyright (c) 2012, 2014, Oracle and/or its affiliates. All rights reserved.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; version 2 of the License.

   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 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 NDB_CONFLICT_H
#define NDB_CONFLICT_H

#include "ndb_conflict_trans.h"
#include <ndbapi/NdbDictionary.hpp>
#include <ndbapi/NdbTransaction.hpp>

#include <mysql_com.h>       // NAME_CHAR_LEN
#include <sql_const.h>       // MAX_REF_PARTS

enum enum_conflict_fn_type
{
  CFT_NDB_UNDEF = 0
  ,CFT_NDB_MAX
  ,CFT_NDB_OLD
  ,CFT_NDB_MAX_DEL_WIN
  ,CFT_NDB_EPOCH
  ,CFT_NDB_EPOCH_TRANS
  ,CFT_NUMBER_OF_CFTS /* End marker */
};

#ifdef HAVE_NDB_BINLOG
static const Uint32 MAX_CONFLICT_ARGS= 8;

enum enum_conflict_fn_arg_type
{
  CFAT_END
  ,CFAT_COLUMN_NAME
  ,CFAT_EXTRA_GCI_BITS
};

struct st_conflict_fn_arg
{
  enum_conflict_fn_arg_type type;
  union
  {
    char resolveColNameBuff[ NAME_CHAR_LEN + 1 ]; // CFAT_COLUMN_NAME
    uint32 extraGciBits; // CFAT_EXTRA_GCI_BITS
  };
};

struct st_conflict_fn_arg_def
{
  enum enum_conflict_fn_arg_type arg_type;
  bool optional;
};

/* What type of operation was issued */
enum enum_conflicting_op_type
{                /* NdbApi          */
  WRITE_ROW   = 1, /* insert (!write) */
  UPDATE_ROW  = 2, /* update          */
  DELETE_ROW  = 3, /* delete          */
  REFRESH_ROW = 4  /* refresh         */
};

/*
  Room for 10 instruction words, two labels (@ 2words/label)
  + 2 extra words for the case of resolve_size == 8
*/
#define MAX_CONFLICT_INTERPRETED_PROG_SIZE 16

/*
  prepare_detect_func

  Type of function used to prepare for conflict detection on
  an NdbApi operation
*/
typedef int (* prepare_detect_func) (struct st_ndbcluster_conflict_fn_share* cfn_share,
                                     enum_conflicting_op_type op_type,
                                     const NdbRecord* data_record,
                                     const uchar* old_data,
                                     const uchar* new_data,
                                     /* Before image columns bitmap */
                                     const MY_BITMAP* bi_cols,
                                     /* After image columns bitmap */
                                     const MY_BITMAP* ai_cols,
                                     class NdbInterpretedCode* code);

enum enum_conflict_fn_flags
{
  CF_TRANSACTIONAL = 1
};

struct st_conflict_fn_def
{
  const char *name;
  enum_conflict_fn_type type;
  const st_conflict_fn_arg_def* arg_defs;
  prepare_detect_func prep_func;
  uint8 flags; /* enum_conflict_fn_flags */
};

/* What sort of conflict was found */
enum enum_conflict_cause
{
  ROW_ALREADY_EXISTS = 1, /* On insert */
  ROW_DOES_NOT_EXIST = 2, /* On Update, Delete */
  ROW_IN_CONFLICT    = 3, /* On Update, Delete */
  TRANS_IN_CONFLICT  = 4  /* Any of above, or implied by transaction */
};

/* NdbOperation custom data which points out handler and record. */
struct Ndb_exceptions_data {
  struct NDB_SHARE* share;
  const NdbRecord* key_rec;
  const NdbRecord* data_rec;
  const uchar* old_row;
  const uchar* new_row;
  my_bitmap_map* bitmap_buf; /* Buffer for write_set */
  MY_BITMAP* write_set;
  enum_conflicting_op_type op_type;
  Uint64 trans_id;
};

enum enum_conflict_fn_table_flags
{
  CFF_NONE         = 0,
  CFF_REFRESH_ROWS = 1
};

/*
   Maximum supported key parts (16)
   (Ndb supports 32, but MySQL has a lower limit)
*/
static const int NDB_MAX_KEY_PARTS = MAX_REF_PARTS;


#define NDB_EXCEPTIONS_TABLE_COLUMN_PREFIX "NDB$"
#define NDB_EXCEPTIONS_TABLE_OP_TYPE "NDB$OP_TYPE"
#define NDB_EXCEPTIONS_TABLE_CONFLICT_CAUSE "NDB$CFT_CAUSE"
#define NDB_EXCEPTIONS_TABLE_ORIG_TRANSID "NDB$ORIG_TRANSID"
#define NDB_EXCEPTIONS_TABLE_COLUMN_OLD_SUFFIX "$OLD"
#define NDB_EXCEPTIONS_TABLE_COLUMN_NEW_SUFFIX "$NEW"

/**
   ExceptionsTableWriter

   Helper class for inserting entries into an exceptions
   table
*/
class ExceptionsTableWriter
{
typedef enum column_version {
  DEFAULT = 0,
  OLD = 1,
  NEW = 2
} COLUMN_VERSION;

public:
  ExceptionsTableWriter()
    : m_pk_cols(0), m_cols(0), m_xcols(0), m_ex_tab(NULL),
    m_count(0), m_extended(false), m_op_type_pos(0), m_conflict_cause_pos(0),
    m_orig_transid_pos(0)
  {};

  ~ExceptionsTableWriter()
  {};

  /**
     hasTable

     Returns true if there is an Exceptions table
  */
  bool hasTable() const
  {
    return m_ex_tab != NULL;
  };

  /**
    init

    Initialise ExceptionsTableWriter with main and exceptions
    tables.

    May set a warning message on success or error.
  */
  int init(const NdbDictionary::Table* mainTable,
           const NdbDictionary::Table* exceptionsTable,
           char* msg_buf,
           uint msg_buf_len,
           const char** msg);

  /**
     free

     Release reference to exceptions table
  */
  void free(Ndb* ndb);

  /**
     writeRow

     Write a row to the Exceptions Table for the given
     key
  */
  int writeRow(NdbTransaction* trans,
               const NdbRecord* keyRecord,
               const NdbRecord* dataRecord,
               uint32 server_id,
               uint32 master_server_id,
               uint64 master_epoch,
               const uchar* oldRowPtr,
               const uchar* newRowPtr,
               enum_conflicting_op_type op_type,
               enum_conflict_cause conflict_cause,
               uint64 orig_transid,
               const MY_BITMAP *write_set,
               NdbError& err);

private:
  /* Help methods for checking exception table definition */
  bool check_mandatory_columns(const NdbDictionary::Table* exceptionsTable);
  bool check_pk_columns(const NdbDictionary::Table* mainTable,
                        const NdbDictionary::Table* exceptionsTable,
                        int &k);
  bool check_optional_columns(const NdbDictionary::Table* mainTable,
                              const NdbDictionary::Table* exceptionsTable,
                              char* msg_buf,
                              uint msg_buf_len,
                              const char** msg,
                              int &k,
                              char *error_details,
                              uint error_details_len);

  /* info about original table */
  uint8 m_pk_cols;
  uint16 m_cols;
  /* Specifies if a column in the original table is nullable */
  bool m_col_nullable[ NDB_MAX_ATTRIBUTES_IN_TABLE ];

  /* info about exceptions table */
  uint16 m_xcols;
  const NdbDictionary::Table *m_ex_tab;
  uint32 m_count;
  /*
    Extension tables can be extended with optional fields
    NDB@OPT_TYPE
   */
  bool m_extended;
  uint32 m_op_type_pos;
  uint32 m_conflict_cause_pos;
  uint32 m_orig_transid_pos;

  /*
    Mapping of where the referenced primary key fields are
    in the original table. Doesn't have to include all fields.
  */
  uint16 m_key_attrids[ NDB_MAX_KEY_PARTS ];
  /* Mapping of pk columns in original table to conflict table */
  int m_key_data_pos[ NDB_MAX_KEY_PARTS ];
  /* Mapping of non-pk columns in original table to conflict table */
  int m_data_pos[ NDB_MAX_ATTRIBUTES_IN_TABLE ];
  /* Specifies what version of a column is reference (before- or after-image) */
  COLUMN_VERSION m_column_version[ NDB_MAX_ATTRIBUTES_IN_TABLE ];

  /*
    has_prefix_ci

    Return true if a column has a specific prefix.
  */
  bool has_prefix_ci(const char *col_name, const char *prefix, CHARSET_INFO *cs);

/*
  has_suffix_ci
  
  Return true if a column has a specific suffix
  and sets the column_real_name to the column name
  without the suffix.
*/
bool has_suffix_ci(const char *col_name,
                   const char *suffix,
                   CHARSET_INFO *cs,
                   char *col_name_real);

/*
  find_column_name_ci

  Search for column_name in table and
  return true if found. Also return what
  position column was found in pos and possible
  position in the primary key in key_pos.
 */
bool find_column_name_ci(CHARSET_INFO *cs,
                         const char *col_name,
                         const NdbDictionary::Table* table,
                         int *pos,
                         int *no_key_cols);
};

typedef struct st_ndbcluster_conflict_fn_share {
  const st_conflict_fn_def* m_conflict_fn;

  /* info about original table */
  uint16 m_resolve_column;
  uint8 m_resolve_size;
  uint8 m_flags;

  ExceptionsTableWriter m_ex_tab_writer;
} NDB_CONFLICT_FN_SHARE;


/* HAVE_NDB_BINLOG */
#endif

/**
 * enum_slave_conflict_role
 *
 * These are the roles the Slave can play
 * in asymmetric conflict algorithms
 */

enum enum_slave_conflict_role
{
  SCR_NONE = 0,
  SCR_SECONDARY = 1,
  SCR_PRIMARY = 2,
  SCR_PASS = 3
};

enum enum_slave_trans_conflict_apply_state
{
  /* Normal with optional row-level conflict detection */
  SAS_NORMAL,

  /*
    SAS_TRACK_TRANS_DEPENDENCIES
    Track inter-transaction dependencies
  */
  SAS_TRACK_TRANS_DEPENDENCIES,

  /*
    SAS_APPLY_TRANS_DEPENDENCIES
    Apply only non conflicting transactions
  */
  SAS_APPLY_TRANS_DEPENDENCIES
};

enum enum_slave_conflict_flags
{
  /* Conflict detection Ops defined */
  SCS_OPS_DEFINED = 1,
  /* Conflict detected on table with transactional resolution */
  SCS_TRANS_CONFLICT_DETECTED_THIS_PASS = 2
};

/*
  State associated with the Slave thread
  (From the Ndb handler's point of view)
*/
struct st_ndb_slave_state
{
  /* Counter values for current slave transaction */
  Uint32 current_violation_count[CFT_NUMBER_OF_CFTS];
  
  /* Track the current epoch from the immediate master,
   * and whether we've committed it
   */
  Uint64 current_master_server_epoch;
  bool current_master_server_epoch_committed;
  
  Uint64 current_max_rep_epoch;
  uint8 conflict_flags; /* enum_slave_conflict_flags */
    /* Transactional conflict detection */
  Uint32 retry_trans_count;
  Uint32 current_trans_row_conflict_count;
  Uint32 current_trans_row_reject_count;
  Uint32 current_trans_in_conflict_count;

  /* Last conflict epoch */
  Uint64 last_conflicted_epoch;

  /* Cumulative counter values */
  Uint64 total_violation_count[CFT_NUMBER_OF_CFTS];
  Uint64 max_rep_epoch;
  Uint32 sql_run_id;
  /* Transactional conflict detection */
  Uint64 trans_row_conflict_count;
  Uint64 trans_row_reject_count;
  Uint64 trans_detect_iter_count;
  Uint64 trans_in_conflict_count;
  Uint64 trans_conflict_commit_count;

  static const Uint32 MAX_RETRY_TRANS_COUNT = 100;

  /*
    Slave Apply State

    State of Binlog application from Ndb point of view.
  */
  enum_slave_trans_conflict_apply_state trans_conflict_apply_state;

  MEM_ROOT conflict_mem_root;
  class DependencyTracker* trans_dependency_tracker;

  /* Methods */
  void atStartSlave();
  int  atPrepareConflictDetection(const NdbDictionary::Table* table,
                                  const NdbRecord* key_rec,
                                  const uchar* row_data,
                                  Uint64 transaction_id,
                                  bool& handle_conflict_now);
  int  atTransConflictDetected(Uint64 transaction_id);
  int  atConflictPreCommit(bool& retry_slave_trans);

  void atBeginTransConflictHandling();
  void atEndTransConflictHandling();

  void atTransactionCommit(Uint64 epoch);
  void atTransactionAbort();
  void atResetSlave();

  int atApplyStatusWrite(Uint32 master_server_id,
                         Uint32 row_server_id,
                         Uint64 row_epoch,
                         bool is_row_server_id_local);
  bool verifyNextEpoch(Uint64 next_epoch,
                       Uint32 master_server_id) const;

  void resetPerAttemptCounters();

  static
  bool checkSlaveConflictRoleChange(enum_slave_conflict_role old_role,
                                    enum_slave_conflict_role new_role,
                                    const char** failure_cause);

  st_ndb_slave_state();
};


/* NDB_CONFLICT_H */
#endif
